From f9433959ff2db3db95ee1e5fb9868b86fb4ceba2 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Thu, 21 May 2026 10:36:20 +0100 Subject: [PATCH 1/9] STRATCONN-6824 - [Braze] - add ecommerce addtocart event --- .../destinations/braze/ecommerce/constants.ts | 2 +- .../destinations/braze/ecommerce/functions.ts | 28 ++++++------- .../src/destinations/braze/ecommerce/types.ts | 40 +++++++++++++------ 3 files changed, 42 insertions(+), 28 deletions(-) diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/constants.ts b/packages/destination-actions/src/destinations/braze/ecommerce/constants.ts index 52599d1b666..ea60e06e9e0 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/constants.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/constants.ts @@ -1,7 +1,7 @@ export const EVENT_NAMES = { PRODUCT_VIEWED: 'ecommerce.product_viewed', CHECKOUT_STARTED: 'ecommerce.checkout_started', - //CART_UPDATED: 'ecommerce.cart_updated', + CART_UPDATED: 'ecommerce.cart_updated', ORDER_PLACED: 'ecommerce.order_placed', ORDER_CANCELLED: 'ecommerce.order_cancelled', ORDER_REFUNDED: 'ecommerce.order_refunded' diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts b/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts index e2c1a56af06..d309e2ab65c 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts @@ -18,7 +18,7 @@ import type { ProductViewedEventName, ProductViewedEvent, MultiProductBaseEvent, - //CartUpdatedEvent, + CartUpdatedEvent, CheckoutStartedEvent, OrderPlacedEvent, OrderRefundedEvent, @@ -172,7 +172,7 @@ function getJSONItem(payload: Payload | SingleProductPayload, settings: Settings } return event } - // case EVENT_NAMES.CART_UPDATED: + case EVENT_NAMES.CART_UPDATED: case EVENT_NAMES.CHECKOUT_STARTED: case EVENT_NAMES.ORDER_PLACED: case EVENT_NAMES.ORDER_CANCELLED: @@ -192,19 +192,19 @@ function getJSONItem(payload: Payload | SingleProductPayload, settings: Settings } switch(name) { - // case EVENT_NAMES.CART_UPDATED: { - // const { cart_id } = payload + case EVENT_NAMES.CART_UPDATED: { + const { cart_id } = payload - // const event: CartUpdatedEvent = { - // ...multiProductEvent, - // name: EVENT_NAMES.CART_UPDATED, - // properties: { - // ...multiProductEvent.properties, - // cart_id: cart_id as string - // } - // } - // return event - // } + const event: CartUpdatedEvent = { + ...multiProductEvent, + name: EVENT_NAMES.CART_UPDATED, + properties: { + ...multiProductEvent.properties, + cart_id: cart_id as string + } + } + return event + } case EVENT_NAMES.CHECKOUT_STARTED: { const { diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/types.ts b/packages/destination-actions/src/destinations/braze/ecommerce/types.ts index b7882dc20d0..5b887e5060d 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/types.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/types.ts @@ -10,14 +10,14 @@ export type PayloadWithIndex = (Payload | SingleProductPayload) & { export type ProductViewedEventName = typeof EVENT_NAMES.PRODUCT_VIEWED export type CheckoutStartedEventName = typeof EVENT_NAMES.CHECKOUT_STARTED -//export type CartUpdatedEventName = typeof EVENT_NAMES.CART_UPDATED +export type CartUpdatedEventName = typeof EVENT_NAMES.CART_UPDATED export type OrderPlacedEventName = typeof EVENT_NAMES.ORDER_PLACED export type OrderCancelledEventName = typeof EVENT_NAMES.ORDER_CANCELLED export type OrderRefundedEventName = typeof EVENT_NAMES.ORDER_REFUNDED export type MultiPropertyEventName = CheckoutStartedEventName | - //CartUpdatedEventName | + CartUpdatedEventName | OrderPlacedEventName | OrderCancelledEventName | OrderRefundedEventName @@ -28,7 +28,7 @@ export interface EcommerceEvents { export type EcommerceEvent = | ProductViewedEvent - // | CartUpdatedEvent + | CartUpdatedEvent | CheckoutStartedEvent | OrderPlacedEvent | OrderRefundedEvent @@ -85,21 +85,28 @@ export interface BaseProduct { product_url?: string } -// export interface CartUpdatedEvent extends MultiProductBaseEvent { -// name: CartUpdatedEventName -// properties: MultiProductBaseEvent['properties'] & { -// cart_id: string -// } -// } +export interface CartUpdatedEvent extends MultiProductBaseEvent { + name: CartUpdatedEventName + properties: MultiProductBaseEvent['properties'] & { + cart_id: string + action?: 'add' | 'remove' | 'replace' + subtotal_value?: number + tax?: number + shipping?: number + } +} export interface CheckoutStartedEvent extends MultiProductBaseEvent { name: CheckoutStartedEventName properties: MultiProductBaseEvent['properties'] & { - checkout_id: string - cart_id?: string - metadata?: BaseEvent['properties']['metadata'] & { + checkout_id: string + cart_id?: string + subtotal_value?: number + tax?: number + shipping?: number + metadata?: BaseEvent['properties']['metadata'] & { checkout_url?: string - } + } } } @@ -108,6 +115,9 @@ export interface OrderPlacedEvent extends MultiProductBaseEvent { properties: MultiProductBaseEvent['properties'] & { order_id: string cart_id?: string + subtotal_value?: number + tax?: number + shipping?: number total_discounts?: number discounts?: Array<{ code: string @@ -123,6 +133,7 @@ export interface OrderRefundedEvent extends MultiProductBaseEvent { name: OrderRefundedEventName properties: MultiProductBaseEvent['properties'] & { order_id: string + subtotal_value?: number total_discounts?: number discounts?: Array<{ code: string @@ -138,6 +149,9 @@ export interface OrderCancelledEvent extends MultiProductBaseEvent { name: OrderCancelledEventName properties: MultiProductBaseEvent['properties'] & { order_id: string + subtotal_value?: number + tax?: number + shipping?: number cancel_reason: string total_discounts?: number discounts?: Array<{ From bca9f37bac7de573d74c579bb4a68aa43fcec743 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Thu, 21 May 2026 11:40:10 +0100 Subject: [PATCH 2/9] adding add to cart --- .../destinations/braze/ecommerce/fields.ts | 173 +++++++++++++++--- .../destinations/braze/ecommerce/functions.ts | 70 ++++--- .../braze/ecommerce/generated-types.ts | 16 ++ 3 files changed, 210 insertions(+), 49 deletions(-) diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts b/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts index 541969ebbf0..fa50db95a41 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts @@ -9,7 +9,7 @@ const name: InputField = { required: true, choices: [ { label: 'Product Viewed', value: EVENT_NAMES.PRODUCT_VIEWED }, - //{ label: 'Cart Updated', value: EVENT_NAMES.CART_UPDATED }, + { label: 'Cart Updated', value: EVENT_NAMES.CART_UPDATED }, { label: 'Checkout Started', value: EVENT_NAMES.CHECKOUT_STARTED }, { label: 'Order Placed', value: EVENT_NAMES.ORDER_PLACED }, { label: 'Order Cancelled', value: EVENT_NAMES.ORDER_CANCELLED }, @@ -201,24 +201,14 @@ const cart_id: InputField = { description: 'Unique identifier for the cart. If no value is passed, Braze will determine a default value (shared across cart, checkout, and order events) for the user cart mapping.', type: 'string', default: {'@path': '$.properties.cart_id'}, - // required: { - // match: 'any', - // conditions: [ - // { - // fieldKey: 'name', - // operator: 'is', - // value: [EVENT_NAMES.CART_UPDATED] - // } - // ] - // }, depends_on: { match: 'any', conditions: [ - // { - // fieldKey: 'name', - // operator: 'is', - // value: EVENT_NAMES.CART_UPDATED - // }, + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.CART_UPDATED + }, { fieldKey: 'name', operator: 'is', @@ -241,16 +231,16 @@ const total_value: InputField = { required: { match: 'any', conditions: [ + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.CART_UPDATED + }, { fieldKey: 'name', operator: 'is', value: EVENT_NAMES.CHECKOUT_STARTED }, - // { - // fieldKey: 'name', - // operator: 'is', - // value: EVENT_NAMES.CART_UPDATED - // }, { fieldKey: 'name', operator: 'is', @@ -271,16 +261,16 @@ const total_value: InputField = { depends_on: { match: 'any', conditions: [ + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.CART_UPDATED + }, { fieldKey: 'name', operator: 'is', value: EVENT_NAMES.CHECKOUT_STARTED }, - // { - // fieldKey: 'name', - // operator: 'is', - // value: EVENT_NAMES.CART_UPDATED - // }, { fieldKey: 'name', operator: 'is', @@ -300,6 +290,133 @@ const total_value: InputField = { } } +const action: InputField = { + label: 'Action', + description: 'The cart action that was performed (add, remove, or replace).', + type: 'string', + default: {'@path': '$.properties.action'}, + choices: [ + { label: 'Add', value: 'add' }, + { label: 'Remove', value: 'remove' }, + { label: 'Replace', value: 'replace' } + ], + required: false, + depends_on: { + match: 'any', + conditions: [ + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.CART_UPDATED + } + ] + } +} + +const subtotal_value: InputField = { + label: 'Subtotal Value', + description: 'Subtotal monetary value of the cart before tax and shipping.', + type: 'number', + default: { '@path': '$.properties.subtotal'}, + required: false, + depends_on: { + match: 'any', + conditions: [ + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.CART_UPDATED + }, + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.CHECKOUT_STARTED + }, + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.ORDER_PLACED + }, + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.ORDER_CANCELLED + }, + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.ORDER_REFUNDED + } + ] + } +} + +const tax: InputField = { + label: 'Tax', + description: 'Tax amount applied to the transaction.', + type: 'number', + default: { '@path': '$.properties.tax'}, + required: false, + depends_on: { + match: 'any', + conditions: [ + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.CART_UPDATED + }, + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.CHECKOUT_STARTED + }, + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.ORDER_PLACED + }, + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.ORDER_CANCELLED + } + ] + } +} + +const shipping: InputField = { + label: 'Shipping', + description: 'Shipping cost for the transaction.', + type: 'number', + default: { '@path': '$.properties.shipping'}, + required: false, + depends_on: { + match: 'any', + conditions: [ + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.CART_UPDATED + }, + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.CHECKOUT_STARTED + }, + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.ORDER_PLACED + }, + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.ORDER_CANCELLED + } + ] + } +} + const total_discounts: InputField = { label: 'Total Discounts', description: 'Total amount of discounts applied to the order.', @@ -558,6 +675,10 @@ export const commonFields = { order_id, cart_id, total_value, + action, + subtotal_value, + tax, + shipping, total_discounts, discounts, currency, diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts b/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts index d309e2ab65c..28c33e8a433 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts @@ -193,72 +193,90 @@ function getJSONItem(payload: Payload | SingleProductPayload, settings: Settings switch(name) { case EVENT_NAMES.CART_UPDATED: { - const { cart_id } = payload + const { cart_id, action, subtotal_value, tax, shipping } = payload as Payload const event: CartUpdatedEvent = { ...multiProductEvent, name: EVENT_NAMES.CART_UPDATED, properties: { ...multiProductEvent.properties, - cart_id: cart_id as string + cart_id: cart_id as string, + ...(action ? { action: action as 'add' | 'remove' | 'replace' } : {}), + ...(typeof subtotal_value === 'number' ? { subtotal_value } : {}), + ...(typeof tax === 'number' ? { tax } : {}), + ...(typeof shipping === 'number' ? { shipping } : {}) } } return event } case EVENT_NAMES.CHECKOUT_STARTED: { - const { - checkout_id, - cart_id - } = payload - + const { + checkout_id, + cart_id, + subtotal_value, + tax, + shipping + } = payload as Payload + const event: CheckoutStartedEvent = { ...multiProductEvent, name: EVENT_NAMES.CHECKOUT_STARTED, properties: { ...multiProductEvent.properties, checkout_id: checkout_id as string, - ...(cart_id ? { cart_id } : {}) + ...(cart_id ? { cart_id } : {}), + ...(typeof subtotal_value === 'number' ? { subtotal_value } : {}), + ...(typeof tax === 'number' ? { tax } : {}), + ...(typeof shipping === 'number' ? { shipping } : {}) } } return event } case EVENT_NAMES.ORDER_PLACED: { - const { + const { order_id, - cart_id, - total_discounts, + cart_id, + subtotal_value, + tax, + shipping, + total_discounts, discounts - } = payload - + } = payload as Payload + const event: OrderPlacedEvent = { ...multiProductEvent, name: EVENT_NAMES.ORDER_PLACED, properties: { ...multiProductEvent.properties, order_id: order_id as string, + ...(cart_id ? { cart_id } : {}), + ...(typeof subtotal_value === 'number' ? { subtotal_value } : {}), + ...(typeof tax === 'number' ? { tax } : {}), + ...(typeof shipping === 'number' ? { shipping } : {}), ...(typeof total_discounts === 'number' ? { total_discounts } : {}), - ...(discounts ? { discounts } : {}), - ...(cart_id ? { cart_id } : {}) + ...(discounts ? { discounts } : {}) } } return event } case EVENT_NAMES.ORDER_REFUNDED: { - const { + const { order_id, - total_discounts, + subtotal_value, + total_discounts, discounts - } = payload - + } = payload as Payload + const event: OrderRefundedEvent = { ...multiProductEvent, name: EVENT_NAMES.ORDER_REFUNDED, properties: { ...multiProductEvent.properties, order_id: order_id as string, + ...(typeof subtotal_value === 'number' ? { subtotal_value } : {}), ...(typeof total_discounts === 'number' ? { total_discounts } : {}), ...(discounts ? { discounts } : {}) } @@ -267,13 +285,16 @@ function getJSONItem(payload: Payload | SingleProductPayload, settings: Settings } case EVENT_NAMES.ORDER_CANCELLED: { - const { + const { order_id, cancel_reason, - total_discounts, + subtotal_value, + tax, + shipping, + total_discounts, discounts - } = payload - + } = payload as Payload + const event: OrderCancelledEvent = { ...multiProductEvent, name: EVENT_NAMES.ORDER_CANCELLED, @@ -281,6 +302,9 @@ function getJSONItem(payload: Payload | SingleProductPayload, settings: Settings ...multiProductEvent.properties, order_id: order_id as string, cancel_reason: cancel_reason as string, + ...(typeof subtotal_value === 'number' ? { subtotal_value } : {}), + ...(typeof tax === 'number' ? { tax } : {}), + ...(typeof shipping === 'number' ? { shipping } : {}), ...(typeof total_discounts === 'number' ? { total_discounts } : {}), ...(discounts ? { discounts } : {}) } diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/generated-types.ts b/packages/destination-actions/src/destinations/braze/ecommerce/generated-types.ts index 29e29851bdc..f991358aee4 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/generated-types.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/generated-types.ts @@ -52,6 +52,22 @@ export interface Payload { * Total monetary value of the cart. */ total_value?: number + /** + * The cart action that was performed (add, remove, or replace). + */ + action?: string + /** + * Subtotal monetary value of the cart before tax and shipping. + */ + subtotal_value?: number + /** + * Tax amount applied to the transaction. + */ + tax?: number + /** + * Shipping cost for the transaction. + */ + shipping?: number /** * Total amount of discounts applied to the order. */ From 4418b2486947c64c7c62005d1bc1e458c5f68fc4 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Thu, 21 May 2026 11:50:34 +0100 Subject: [PATCH 3/9] some fields not needed for refunds --- .../src/destinations/braze/ecommerce/fields.ts | 5 ----- .../src/destinations/braze/ecommerce/functions.ts | 2 -- .../src/destinations/braze/ecommerce/types.ts | 1 - 3 files changed, 8 deletions(-) diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts b/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts index fa50db95a41..033c64dc338 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts @@ -341,11 +341,6 @@ const subtotal_value: InputField = { fieldKey: 'name', operator: 'is', value: EVENT_NAMES.ORDER_CANCELLED - }, - { - fieldKey: 'name', - operator: 'is', - value: EVENT_NAMES.ORDER_REFUNDED } ] } diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts b/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts index 28c33e8a433..e0da382fc18 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts @@ -265,7 +265,6 @@ function getJSONItem(payload: Payload | SingleProductPayload, settings: Settings case EVENT_NAMES.ORDER_REFUNDED: { const { order_id, - subtotal_value, total_discounts, discounts } = payload as Payload @@ -276,7 +275,6 @@ function getJSONItem(payload: Payload | SingleProductPayload, settings: Settings properties: { ...multiProductEvent.properties, order_id: order_id as string, - ...(typeof subtotal_value === 'number' ? { subtotal_value } : {}), ...(typeof total_discounts === 'number' ? { total_discounts } : {}), ...(discounts ? { discounts } : {}) } diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/types.ts b/packages/destination-actions/src/destinations/braze/ecommerce/types.ts index 5b887e5060d..0e5832b5517 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/types.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/types.ts @@ -133,7 +133,6 @@ export interface OrderRefundedEvent extends MultiProductBaseEvent { name: OrderRefundedEventName properties: MultiProductBaseEvent['properties'] & { order_id: string - subtotal_value?: number total_discounts?: number discounts?: Array<{ code: string From bab79f2502da4d83a5319b5e6a2cd67b256f28f9 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Thu, 21 May 2026 12:44:58 +0100 Subject: [PATCH 4/9] adding type field for product viewed event --- .../destinations/braze/ecommerce/fields.ts | 27 ++++++++++++++++++- .../destinations/braze/ecommerce/functions.ts | 6 +++-- .../src/destinations/braze/ecommerce/types.ts | 4 ++- .../ecommerceSingleProduct/generated-types.ts | 4 +++ .../braze/ecommerceSingleProduct/index.ts | 4 ++- 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts b/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts index 033c64dc338..0060e1d177e 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts @@ -639,6 +639,29 @@ const metadata: InputField = { defaultObjectUI: 'keyvalue' } +const catalog_type: InputField = { + label: 'Catalog Trigger Type', + description: 'Required to use Braze catalog trigger features. Accepted values: price_drop, back_in_stock.', + type: 'string', + multiple: true, + choices: [ + { label: 'Price Drop', value: 'price_drop' }, + { label: 'Back In Stock', value: 'back_in_stock' } + ], + default: { '@path': '$.properties.type' }, + required: false, + depends_on: { + match: 'any', + conditions: [ + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.PRODUCT_VIEWED + } + ] + } +} + const enable_batching: InputField = { type: 'boolean', label: 'Batch Data to Braze', @@ -681,4 +704,6 @@ export const commonFields = { metadata, enable_batching, batch_size -} \ No newline at end of file +} + +export { catalog_type } \ No newline at end of file diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts b/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts index e0da382fc18..3a980ba8622 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts @@ -159,7 +159,8 @@ function getJSONItem(payload: Payload | SingleProductPayload, settings: Settings switch(name) { case EVENT_NAMES.PRODUCT_VIEWED: { const { - product + product, + catalog_type } = payload as SingleProductPayload const event: ProductViewedEvent = { @@ -167,7 +168,8 @@ function getJSONItem(payload: Payload | SingleProductPayload, settings: Settings name: EVENT_NAMES.PRODUCT_VIEWED, properties: { ...baseEvent.properties, - ...product + ...product, + ...(catalog_type && catalog_type.length > 0 ? { type: catalog_type } : {}) } } return event diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/types.ts b/packages/destination-actions/src/destinations/braze/ecommerce/types.ts index 0e5832b5517..2b17ec687f2 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/types.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/types.ts @@ -58,7 +58,9 @@ export interface BaseEvent { export interface ProductViewedEvent extends BaseEvent { name: ProductViewedEventName - properties: BaseEvent['properties'] & BaseProduct + properties: BaseEvent['properties'] & BaseProduct & { + type?: string[] + } } export interface MultiProductBaseEvent extends BaseEvent { diff --git a/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/generated-types.ts b/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/generated-types.ts index 1bf9f69d061..07530771487 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/generated-types.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/generated-types.ts @@ -71,6 +71,10 @@ export interface Payload { * Source the event is derived from. */ source: string + /** + * Required to use Braze catalog trigger features. Accepted values: price_drop, back_in_stock. + */ + catalog_type?: string[] /** * Additional metadata for the ecommerce event. */ diff --git a/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/index.ts b/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/index.ts index d9c5692a859..41b317534d0 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/index.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/index.ts @@ -1,9 +1,10 @@ import type { ActionDefinition } from '@segment/actions-core' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' -import { +import { commonFields, product, + catalog_type, } from '../ecommerce/fields' import { send } from '../ecommerce/functions' @@ -12,6 +13,7 @@ const action: ActionDefinition = { description: '(Beta) Send a single product [Ecommerce Recommended event](https://www.braze.com/docs/user_guide/data/activation/custom_data/recommended_events/ecommerce_events) to Braze.', fields: { ...commonFields, + catalog_type, product }, syncMode: { From 602dff76c4195a382269c6f2b3bfbed5aba12f0c Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Thu, 21 May 2026 13:16:34 +0100 Subject: [PATCH 5/9] unit tests --- .../braze/ecommerce/__tests__/index.test.ts | 343 ++++++++++++++++-- .../__tests__/index.test.ts | 70 ++++ 2 files changed, 389 insertions(+), 24 deletions(-) diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/__tests__/index.test.ts b/packages/destination-actions/src/destinations/braze/ecommerce/__tests__/index.test.ts index 32e5abf7a52..49578d3b883 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/__tests__/index.test.ts @@ -30,6 +30,10 @@ const payload = { cart_id: 'cart_id_1', checkout_id: 'checkout_id_1', total: 100.0, + subtotal: 85.0, + tax: 9.0, + shipping: 6.0, + action: 'replace', discount: 10, discount_items: [ { @@ -52,7 +56,7 @@ const payload = { product_url: 'https://example.com/prod1', quantity: 2, price: 25.0, - color: 'red', + color: 'red', size: 'M' }, { @@ -99,6 +103,10 @@ const mapping = { order_id: { '@path': '$.properties.order_id' }, cart_id: { '@path': '$.properties.cart_id' }, total_value: { '@path': '$.properties.total' }, + action: { '@path': '$.properties.action' }, + subtotal_value: { '@path': '$.properties.subtotal' }, + tax: { '@path': '$.properties.tax' }, + shipping: { '@path': '$.properties.shipping' }, total_discounts: { '@path': '$.properties.discount' }, discounts: {'@path': '$.properties.discount_items' }, currency: { '@path': '$.properties.currency' }, @@ -113,7 +121,7 @@ const mapping = { image_url: {'@path': '$.image_url'}, product_url: {'@path': '$.url'}, quantity: {'@path': '$.quantity'}, - price: {'@path': '$.price'}, + price: {'@path': '$.price'}, color: { '@path': '$.color' }, size: { '@path': '$.size' } } @@ -193,12 +201,15 @@ describe('Braze.ecommerce', () => { ], total_value: 100, order_id: "order_id_1", + cart_id: "cart_id_1", + subtotal_value: 85, + tax: 9, + shipping: 6, total_discounts: 10, discounts: [ { code: "SUMMER21", amount: 5 }, { code: "VIPCUSTOMER", amount: 5 } ], - cart_id: "cart_id_1", metadata: { custom_field_1: "custom_value_1", custom_field_2: 100, @@ -232,9 +243,9 @@ describe('Braze.ecommerce', () => { it('should send Checkout Started event correctly', async () => { - const mapping2 = { - ...mapping, - name: EVENT_NAMES.CHECKOUT_STARTED + const mapping2 = { + ...mapping, + name: EVENT_NAMES.CHECKOUT_STARTED } const deepCopy: Partial = JSON.parse(JSON.stringify(payload)) @@ -283,6 +294,9 @@ describe('Braze.ecommerce', () => { total_value: 100, checkout_id: "checkout_id_1", cart_id: "cart_id_1", + subtotal_value: 85, + tax: 9, + shipping: 6, metadata: { custom_field_1: "custom_value_1", custom_field_2: 100, @@ -400,9 +414,9 @@ describe('Braze.ecommerce', () => { it('should send Order Cancelled event correctly', async () => { - const mapping2 = { - ...mapping, - name: EVENT_NAMES.ORDER_CANCELLED + const mapping2 = { + ...mapping, + name: EVENT_NAMES.ORDER_CANCELLED } const deepCopy: Partial = JSON.parse(JSON.stringify(payload)) @@ -451,6 +465,9 @@ describe('Braze.ecommerce', () => { total_value: 100, order_id: "order_id_1", cancel_reason: "I didn't like it", + subtotal_value: 85, + tax: 9, + shipping: 6, total_discounts: 10, discounts: [ { code: "SUMMER21", amount: 5 }, @@ -485,6 +502,180 @@ describe('Braze.ecommerce', () => { expect(response.length).toBe(1) }) + it('should send Cart Updated event with all fields correctly', async () => { + + const mapping2 = { + ...mapping, + name: EVENT_NAMES.CART_UPDATED + } + + const deepCopy: Partial = JSON.parse(JSON.stringify(payload)) + const e = createTestEvent(deepCopy) + delete e.properties?.product + + const json = { + events: [ + { + external_id: "userId1", + braze_id: "braze_id_1", + email: "email@email.com", + phone: "+14155551234", + user_alias: { + alias_name: "alias_name_1", + alias_label: "alias_label_1" + }, + app_id: "test_app_id", + name: "ecommerce.cart_updated", + time: "2024-06-10T12:00:00.000Z", + properties: { + currency: "USD", + source: "test_source", + products: [ + { + product_id: "prod_1", + product_name: "Product 1", + variant_id: "Size M", + image_url: "https://example.com/prod1.jpg", + quantity: 2, + price: 25, + metadata: { + color: "red", + size: "M" + } + }, + { + product_id: "prod_2", + product_name: "Product 2", + variant_id: "Size L", + image_url: "https://example.com/prod2.jpg", + quantity: 1, + price: 50 + } + ], + total_value: 100, + cart_id: "cart_id_1", + action: "replace", + subtotal_value: 85, + tax: 9, + shipping: 6, + metadata: { + custom_field_1: "custom_value_1", + custom_field_2: 100, + custom_field_3: true, + custom_field_4: ["a", "b", "c"], + custom_field_5: { nested_key: "nested_value" }, + checkout_url: "https://example.com/checkout", + order_status_url: "https://example.com/order/status" + } + }, + _update_existing_only: true + } + ] + } + + nock(settings.endpoint) + .post('/users/track', json) + .reply(200) + + const response = await testDestination.testAction('ecommerce', { + event: e, + settings, + useDefaultMappings: true, + mapping: mapping2 + }) + + expect(response.length).toBe(1) + }) + + it('should send Cart Updated event with minimal fields correctly', async () => { + + const mapping2 = { + ...mapping, + name: EVENT_NAMES.CART_UPDATED, + action: undefined, + subtotal_value: undefined, + tax: undefined, + shipping: undefined + } + + const deepCopy: Partial = JSON.parse(JSON.stringify(payload)) + delete deepCopy.properties?.action + delete deepCopy.properties?.subtotal + delete deepCopy.properties?.tax + delete deepCopy.properties?.shipping + const e = createTestEvent(deepCopy) + delete e.properties?.product + + const json = { + events: [ + { + external_id: "userId1", + braze_id: "braze_id_1", + email: "email@email.com", + phone: "+14155551234", + user_alias: { + alias_name: "alias_name_1", + alias_label: "alias_label_1" + }, + app_id: "test_app_id", + name: "ecommerce.cart_updated", + time: "2024-06-10T12:00:00.000Z", + properties: { + currency: "USD", + source: "test_source", + products: [ + { + product_id: "prod_1", + product_name: "Product 1", + variant_id: "Size M", + image_url: "https://example.com/prod1.jpg", + quantity: 2, + price: 25, + metadata: { + color: "red", + size: "M" + } + }, + { + product_id: "prod_2", + product_name: "Product 2", + variant_id: "Size L", + image_url: "https://example.com/prod2.jpg", + quantity: 1, + price: 50 + } + ], + total_value: 100, + cart_id: "cart_id_1", + metadata: { + custom_field_1: "custom_value_1", + custom_field_2: 100, + custom_field_3: true, + custom_field_4: ["a", "b", "c"], + custom_field_5: { nested_key: "nested_value" }, + checkout_url: "https://example.com/checkout", + order_status_url: "https://example.com/order/status" + } + }, + _update_existing_only: true + } + ] + } + + nock(settings.endpoint) + .post('/users/track', json) + .reply(200) + + const response = await testDestination.testAction('ecommerce', { + event: e, + settings, + useDefaultMappings: true, + mapping: mapping2 + }) + + expect(response.length).toBe(1) + }) + it('should throw an error if missing identifier', async () => { const deepCopy: Partial = JSON.parse(JSON.stringify(payload)) @@ -529,15 +720,17 @@ describe('Braze.ecommerce', () => { const deepCopy2: Partial = JSON.parse(JSON.stringify(payload)) const deepCopy3: Partial = JSON.parse(JSON.stringify(payload)) const deepCopy4: Partial = JSON.parse(JSON.stringify(payload)) + const deepCopy5: Partial = JSON.parse(JSON.stringify(payload)) const e1 = createTestEvent({...deepCopy1, userId: 'userId1', event: 'ecommerce.order_placed' }) const e2 = createTestEvent({...deepCopy2, userId: 'userId2', event: 'ecommerce.order_refunded' }) const e3 = createTestEvent({...deepCopy3, userId: 'userId3', event: 'ecommerce.checkout_started' }) const e4 = createTestEvent({...deepCopy4, userId: 'userId4', event: 'ecommerce.order_cancelled' }) - const events = [e1, e2, e3, e4] + const e5 = createTestEvent({...deepCopy5, userId: 'userId5', event: 'ecommerce.cart_updated' }) + const events = [e1, e2, e3, e4, e5] - const mapping2 = { - ...mapping, + const mapping2 = { + ...mapping, name: { '@path': '$.event' }, } @@ -585,12 +778,15 @@ describe('Braze.ecommerce', () => { ], total_value: 100, order_id: "order_id_1", + cart_id: "cart_id_1", + subtotal_value: 85, + tax: 9, + shipping: 6, total_discounts: 10, discounts: [ { code: "SUMMER21", amount: 5 }, { code: "VIPCUSTOMER", amount: 5 } - ], - cart_id: "cart_id_1" + ] }, _update_existing_only: true }, @@ -686,7 +882,10 @@ describe('Braze.ecommerce', () => { ], total_value: 100, checkout_id: "checkout_id_1", - cart_id: "cart_id_1" + cart_id: "cart_id_1", + subtotal_value: 85, + tax: 9, + shipping: 6 }, _update_existing_only: true }, @@ -733,6 +932,9 @@ describe('Braze.ecommerce', () => { total_value: 100, order_id: "order_id_1", cancel_reason: "I didn't like it", + subtotal_value: 85, + tax: 9, + shipping: 6, total_discounts: 10, discounts: [ { code: "SUMMER21", amount: 5 }, @@ -740,6 +942,55 @@ describe('Braze.ecommerce', () => { ] }, _update_existing_only: true + }, + { + external_id: "userId5", + braze_id: "braze_id_1", + email: "email@email.com", + phone: "+14155551234", + user_alias: { alias_name: "alias_name_1", alias_label: "alias_label_1" }, + app_id: "test_app_id", + name: "ecommerce.cart_updated", + time: "2024-06-10T12:00:00.000Z", + properties: { + currency: "USD", + source: "test_source", + metadata: { + custom_field_1: "custom_value_1", + custom_field_2: 100, + custom_field_3: true, + custom_field_4: ["a", "b", "c"], + custom_field_5: { nested_key: "nested_value" }, + checkout_url: "https://example.com/checkout", + order_status_url: "https://example.com/order/status" + }, + products: [ + { + product_id: "prod_1", + product_name: "Product 1", + variant_id: "Size M", + image_url: "https://example.com/prod1.jpg", + quantity: 2, + price: 25, + metadata: { color: "red", size: "M" } + }, + { + product_id: "prod_2", + product_name: "Product 2", + variant_id: "Size L", + image_url: "https://example.com/prod2.jpg", + quantity: 1, + price: 50 + } + ], + total_value: 100, + cart_id: "cart_id_1", + action: "replace", + subtotal_value: 85, + tax: 9, + shipping: 6 + }, + _update_existing_only: true } ] } @@ -754,7 +1005,7 @@ describe('Braze.ecommerce', () => { settings, mapping: mapping2 }) - + expect(response.length).toBe(1) }) @@ -876,7 +1127,10 @@ describe('Braze.ecommerce', () => { ], total_value: 100, checkout_id: "checkout_id_1", - cart_id: "cart_id_1" + cart_id: "cart_id_1", + subtotal_value: 85, + tax: 9, + shipping: 6 }, _update_existing_only: true }, @@ -923,6 +1177,9 @@ describe('Braze.ecommerce', () => { total_value: 100, order_id: "order_id_1", cancel_reason: "I didn't like it", + subtotal_value: 85, + tax: 9, + shipping: 6, total_discounts: 10, discounts: [ { code: "SUMMER21", amount: 5 }, @@ -934,8 +1191,8 @@ describe('Braze.ecommerce', () => { ] } - const mapping2 = { - ...mapping, + const mapping2 = { + ...mapping, name: { '@path': '$.event' }, } @@ -958,6 +1215,10 @@ describe('Braze.ecommerce', () => { "order_id": "order_id_1", "cart_id": "cart_id_1", "total_value": 100, + "action": "replace", + "subtotal_value": 85, + "tax": 9, + "shipping": 6, "total_discounts": 10, "discounts": [ { @@ -1023,6 +1284,10 @@ describe('Braze.ecommerce', () => { "order_id": "order_id_1", "cart_id": "cart_id_1", "total_value": 100, + "action": "replace", + "subtotal_value": 85, + "tax": 9, + "shipping": 6, "total_discounts": 10, "discounts": [ { @@ -1095,6 +1360,10 @@ describe('Braze.ecommerce', () => { "order_id": "order_id_1", "cart_id": "cart_id_1", "total_value": 100, + "action": "replace", + "subtotal_value": 85, + "tax": 9, + "shipping": 6, "total_discounts": 10, "discounts": [ { @@ -1147,7 +1416,7 @@ describe('Braze.ecommerce', () => { "batch_size": 75, "index": 1 }, - "body": "{\"external_id\":\"userId3\",\"braze_id\":\"braze_id_1\",\"email\":\"email@email.com\",\"phone\":\"+14155551234\",\"user_alias\":{\"alias_name\":\"alias_name_1\",\"alias_label\":\"alias_label_1\"},\"app_id\":\"test_app_id\",\"name\":\"ecommerce.checkout_started\",\"time\":\"2024-06-10T12:00:00.000Z\",\"properties\":{\"currency\":\"USD\",\"source\":\"test_source\",\"metadata\":{\"custom_field_1\":\"custom_value_1\",\"custom_field_2\":100,\"custom_field_3\":true,\"custom_field_4\":[\"a\",\"b\",\"c\"],\"custom_field_5\":{\"nested_key\":\"nested_value\"},\"checkout_url\":\"https://example.com/checkout\",\"order_status_url\":\"https://example.com/order/status\"},\"products\":[{\"quantity\":2,\"product_id\":\"prod_1\",\"product_name\":\"Product 1\",\"variant_id\":\"Size M\",\"price\":25,\"image_url\":\"https://example.com/prod1.jpg\",\"metadata\":{\"color\":\"red\",\"size\":\"M\"}},{\"quantity\":1,\"product_id\":\"prod_2\",\"product_name\":\"Product 2\",\"variant_id\":\"Size L\",\"price\":50,\"image_url\":\"https://example.com/prod2.jpg\"}],\"total_value\":100,\"checkout_id\":\"checkout_id_1\",\"cart_id\":\"cart_id_1\"},\"_update_existing_only\":true}" + "body": "{\"external_id\":\"userId3\",\"braze_id\":\"braze_id_1\",\"email\":\"email@email.com\",\"phone\":\"+14155551234\",\"user_alias\":{\"alias_name\":\"alias_name_1\",\"alias_label\":\"alias_label_1\"},\"app_id\":\"test_app_id\",\"name\":\"ecommerce.checkout_started\",\"time\":\"2024-06-10T12:00:00.000Z\",\"properties\":{\"currency\":\"USD\",\"source\":\"test_source\",\"metadata\":{\"custom_field_1\":\"custom_value_1\",\"custom_field_2\":100,\"custom_field_3\":true,\"custom_field_4\":[\"a\",\"b\",\"c\"],\"custom_field_5\":{\"nested_key\":\"nested_value\"},\"checkout_url\":\"https://example.com/checkout\",\"order_status_url\":\"https://example.com/order/status\"},\"products\":[{\"quantity\":2,\"product_id\":\"prod_1\",\"product_name\":\"Product 1\",\"variant_id\":\"Size M\",\"price\":25,\"image_url\":\"https://example.com/prod1.jpg\",\"metadata\":{\"color\":\"red\",\"size\":\"M\"}},{\"quantity\":1,\"product_id\":\"prod_2\",\"product_name\":\"Product 2\",\"variant_id\":\"Size L\",\"price\":50,\"image_url\":\"https://example.com/prod2.jpg\"}],\"total_value\":100,\"checkout_id\":\"checkout_id_1\",\"cart_id\":\"cart_id_1\",\"subtotal_value\":85,\"tax\":9,\"shipping\":6},\"_update_existing_only\":true}" }, { "status": 200, @@ -1167,6 +1436,10 @@ describe('Braze.ecommerce', () => { "order_id": "order_id_1", "cart_id": "cart_id_1", "total_value": 100, + "action": "replace", + "subtotal_value": 85, + "tax": 9, + "shipping": 6, "total_discounts": 10, "discounts": [ { @@ -1219,10 +1492,10 @@ describe('Braze.ecommerce', () => { "batch_size": 75, "index": 2 }, - "body": "{\"external_id\":\"userId4\",\"braze_id\":\"braze_id_1\",\"email\":\"email@email.com\",\"phone\":\"+14155551234\",\"user_alias\":{\"alias_name\":\"alias_name_1\",\"alias_label\":\"alias_label_1\"},\"app_id\":\"test_app_id\",\"name\":\"ecommerce.order_cancelled\",\"time\":\"2024-06-10T12:00:00.000Z\",\"properties\":{\"currency\":\"USD\",\"source\":\"test_source\",\"metadata\":{\"custom_field_1\":\"custom_value_1\",\"custom_field_2\":100,\"custom_field_3\":true,\"custom_field_4\":[\"a\",\"b\",\"c\"],\"custom_field_5\":{\"nested_key\":\"nested_value\"},\"checkout_url\":\"https://example.com/checkout\",\"order_status_url\":\"https://example.com/order/status\"},\"products\":[{\"quantity\":2,\"product_id\":\"prod_1\",\"product_name\":\"Product 1\",\"variant_id\":\"Size M\",\"price\":25,\"image_url\":\"https://example.com/prod1.jpg\",\"metadata\":{\"color\":\"red\",\"size\":\"M\"}},{\"quantity\":1,\"product_id\":\"prod_2\",\"product_name\":\"Product 2\",\"variant_id\":\"Size L\",\"price\":50,\"image_url\":\"https://example.com/prod2.jpg\"}],\"total_value\":100,\"order_id\":\"order_id_1\",\"cancel_reason\":\"I didn't like it\",\"total_discounts\":10,\"discounts\":[{\"code\":\"SUMMER21\",\"amount\":5},{\"code\":\"VIPCUSTOMER\",\"amount\":5}]},\"_update_existing_only\":true}" + "body": "{\"external_id\":\"userId4\",\"braze_id\":\"braze_id_1\",\"email\":\"email@email.com\",\"phone\":\"+14155551234\",\"user_alias\":{\"alias_name\":\"alias_name_1\",\"alias_label\":\"alias_label_1\"},\"app_id\":\"test_app_id\",\"name\":\"ecommerce.order_cancelled\",\"time\":\"2024-06-10T12:00:00.000Z\",\"properties\":{\"currency\":\"USD\",\"source\":\"test_source\",\"metadata\":{\"custom_field_1\":\"custom_value_1\",\"custom_field_2\":100,\"custom_field_3\":true,\"custom_field_4\":[\"a\",\"b\",\"c\"],\"custom_field_5\":{\"nested_key\":\"nested_value\"},\"checkout_url\":\"https://example.com/checkout\",\"order_status_url\":\"https://example.com/order/status\"},\"products\":[{\"quantity\":2,\"product_id\":\"prod_1\",\"product_name\":\"Product 1\",\"variant_id\":\"Size M\",\"price\":25,\"image_url\":\"https://example.com/prod1.jpg\",\"metadata\":{\"color\":\"red\",\"size\":\"M\"}},{\"quantity\":1,\"product_id\":\"prod_2\",\"product_name\":\"Product 2\",\"variant_id\":\"Size L\",\"price\":50,\"image_url\":\"https://example.com/prod2.jpg\"}],\"total_value\":100,\"order_id\":\"order_id_1\",\"cancel_reason\":\"I didn't like it\",\"subtotal_value\":85,\"tax\":9,\"shipping\":6,\"total_discounts\":10,\"discounts\":[{\"code\":\"SUMMER21\",\"amount\":5},{\"code\":\"VIPCUSTOMER\",\"amount\":5}]},\"_update_existing_only\":true}" } ] - + nock(settings.endpoint) .post('/users/track', json) .reply(200) @@ -1353,7 +1626,10 @@ describe('Braze.ecommerce', () => { ], total_value: 100, checkout_id: "checkout_id_1", - cart_id: "cart_id_1" + cart_id: "cart_id_1", + subtotal_value: 85, + tax: 9, + shipping: 6 }, _update_existing_only: true }, @@ -1400,6 +1676,9 @@ describe('Braze.ecommerce', () => { total_value: 100, order_id: "order_id_1", cancel_reason: "I didn't like it", + subtotal_value: 85, + tax: 9, + shipping: 6, total_discounts: 10, discounts: [ { code: "SUMMER21", amount: 5 }, @@ -1411,7 +1690,7 @@ describe('Braze.ecommerce', () => { ] } - const mapping2 = { + const mapping2 = { ...mapping, __segment_internal_sync_mode: '', name: { '@path': '$.event' }, @@ -1423,6 +1702,7 @@ describe('Braze.ecommerce', () => { "errorreporter": "DESTINATION", "errortype": "BAD_REQUEST", "sent": { + "action": "replace", "batch_size": 75, "braze_id": "braze_id_1", "cancel_reason": "I didn't like it", @@ -1468,7 +1748,10 @@ describe('Braze.ecommerce', () => { "variant_id": "Size L" } ], + "shipping": 6, "source": "test_source", + "subtotal_value": 85, + "tax": 9, "time": "2024-06-10T12:00:00.000Z", "total_discounts": 10, "total_value": 100, @@ -1484,6 +1767,7 @@ describe('Braze.ecommerce', () => { "errorreporter": "DESTINATION", "errortype": "BAD_REQUEST", "sent": { + "action": "replace", "batch_size": 75, "cancel_reason": "I didn't like it", "cart_id": "cart_id_1", @@ -1525,7 +1809,10 @@ describe('Braze.ecommerce', () => { "variant_id": "Size L" } ], + "shipping": 6, "source": "test_source", + "subtotal_value": 85, + "tax": 9, "time": "2024-06-10T12:00:00.000Z", "total_discounts": 10, "total_value": 100 @@ -1537,6 +1824,7 @@ describe('Braze.ecommerce', () => { "errorreporter": "DESTINATION", "errortype": "BAD_REQUEST", "sent": { + "action": "replace", "batch_size": 75, "braze_id": "braze_id_1", "cancel_reason": "I didn't like it", @@ -1582,7 +1870,10 @@ describe('Braze.ecommerce', () => { "variant_id": "Size L" } ], + "shipping": 6, "source": "test_source", + "subtotal_value": 85, + "tax": 9, "time": "2024-06-10T12:00:00.000Z", "total_discounts": 10, "total_value": 100, @@ -1598,6 +1889,7 @@ describe('Braze.ecommerce', () => { "errorreporter": "DESTINATION", "errortype": "BAD_REQUEST", "sent": { + "action": "replace", "batch_size": 75, "braze_id": "braze_id_1", "cancel_reason": "I didn't like it", @@ -1643,7 +1935,10 @@ describe('Braze.ecommerce', () => { "variant_id": "Size L" } ], + "shipping": 6, "source": "test_source", + "subtotal_value": 85, + "tax": 9, "time": "2024-06-10T12:00:00.000Z", "total_discounts": 10, "total_value": 100, diff --git a/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/__tests__/index.test.ts b/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/__tests__/index.test.ts index 094fbc1c05e..e473cd66389 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/__tests__/index.test.ts @@ -157,6 +157,76 @@ describe('Braze.ecommerce', () => { }) }) + it('should send Product Viewed event with catalog_type correctly', async () => { + + const deepCopy: Partial = JSON.parse(JSON.stringify(payload)) + deepCopy.properties = { + ...deepCopy.properties, + type: ['price_drop', 'back_in_stock'] + } + const e = createTestEvent(deepCopy) + + const mapping2 = { + ...mapping, + catalog_type: { '@path': '$.properties.type' } + } + + const json = { + events: [ + { + external_id: "userId1", + braze_id: "braze_id_1", + email: "email@email.com", + phone: "+14155551234", + user_alias: { + alias_name: "alias_name_1", + alias_label: "alias_label_1" + }, + app_id: "test_app_id", + name: "ecommerce.product_viewed", + time: "2024-06-10T12:00:00.000Z", + properties: { + currency: "USD", + source: "test_source", + metadata: { + custom_field_1: "custom_value_1", + custom_field_2: 100, + custom_field_3: true, + custom_field_4: ["a", "b", "c"], + custom_field_5: { + nested_key: "nested_value" + }, + checkout_url: "https://example.com/checkout", + order_status_url: "https://example.com/order/status" + }, + product_id: "prod_1", + product_name: "Product 1", + variant_id: "Size M", + image_url: "https://example.com/prod1.jpg", + product_url: "https://example.com/prod1", + price: 25, + type: ["price_drop", "back_in_stock"] + }, + _update_existing_only: true + } + ] + } + + nock(settings.endpoint) + .post('/users/track', json) + .reply(200) + + const response = await testDestination.testAction('ecommerceSingleProduct', { + event: e, + settings, + useDefaultMappings: true, + mapping: mapping2 + }) + + expect(response.length).toBe(1) + }) + }) + describe('batch events', () => { it('should send batched single product ecommerce events correctly', async () => { From 0827a3896f09eb707bef5fa2b448d83edc9ed6d4 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Thu, 21 May 2026 13:26:45 +0100 Subject: [PATCH 6/9] making cart_id conditionally required --- .../destinations/braze/ecommerce/fields.ts | 12 ++++++++- .../braze/ecommerce/generated-types.ts | 2 +- .../ecommerceSingleProduct/generated-types.ts | 26 +++++++++++++++---- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts b/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts index 0060e1d177e..e3b1aa4d805 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts @@ -198,9 +198,19 @@ const order_id: InputField = { const cart_id: InputField = { label: 'Cart ID', - description: 'Unique identifier for the cart. If no value is passed, Braze will determine a default value (shared across cart, checkout, and order events) for the user cart mapping.', + description: 'Unique identifier for the cart. Required for cart_updated. For checkout and order events, if no value is passed, Braze will determine a default value for the user cart mapping.', type: 'string', default: {'@path': '$.properties.cart_id'}, + required: { + match: 'any', + conditions: [ + { + fieldKey: 'name', + operator: 'is', + value: EVENT_NAMES.CART_UPDATED + } + ] + }, depends_on: { match: 'any', conditions: [ diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/generated-types.ts b/packages/destination-actions/src/destinations/braze/ecommerce/generated-types.ts index f991358aee4..d19a8f0854b 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/generated-types.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/generated-types.ts @@ -45,7 +45,7 @@ export interface Payload { */ order_id?: string /** - * Unique identifier for the cart. If no value is passed, Braze will determine a default value (shared across cart, checkout, and order events) for the user cart mapping. + * Unique identifier for the cart. Required for cart_updated. For checkout and order events, if no value is passed, Braze will determine a default value for the user cart mapping. */ cart_id?: string /** diff --git a/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/generated-types.ts b/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/generated-types.ts index 07530771487..8501e7c43ad 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/generated-types.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/generated-types.ts @@ -45,13 +45,29 @@ export interface Payload { */ order_id?: string /** - * Unique identifier for the cart. If no value is passed, Braze will determine a default value (shared across cart, checkout, and order events) for the user cart mapping. + * Unique identifier for the cart. Required for cart_updated. For checkout and order events, if no value is passed, Braze will determine a default value for the user cart mapping. */ cart_id?: string /** * Total monetary value of the cart. */ total_value?: number + /** + * The cart action that was performed (add, remove, or replace). + */ + action?: string + /** + * Subtotal monetary value of the cart before tax and shipping. + */ + subtotal_value?: number + /** + * Tax amount applied to the transaction. + */ + tax?: number + /** + * Shipping cost for the transaction. + */ + shipping?: number /** * Total amount of discounts applied to the order. */ @@ -71,10 +87,6 @@ export interface Payload { * Source the event is derived from. */ source: string - /** - * Required to use Braze catalog trigger features. Accepted values: price_drop, back_in_stock. - */ - catalog_type?: string[] /** * Additional metadata for the ecommerce event. */ @@ -89,6 +101,10 @@ export interface Payload { * Maximum number of events to include in each batch. Actual batch sizes may be lower. */ batch_size: number + /** + * Required to use Braze catalog trigger features. Accepted values: price_drop, back_in_stock. + */ + catalog_type?: string[] /** * Product details associated with the ecommerce event. */ From 69f512abd247bdbcdc546c8bbe7341561272a81a Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Thu, 21 May 2026 13:51:24 +0100 Subject: [PATCH 7/9] adding presets --- .../__tests__/index.test.ts | 3 +- .../src/destinations/braze/index.ts | 48 ++++++++++++++++++- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/__tests__/index.test.ts b/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/__tests__/index.test.ts index e473cd66389..a5aad83d0b1 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/__tests__/index.test.ts @@ -154,8 +154,7 @@ describe('Braze.ecommerce', () => { }) expect(response.length).toBe(1) - }) - }) + }) it('should send Product Viewed event with catalog_type correctly', async () => { diff --git a/packages/destination-actions/src/destinations/braze/index.ts b/packages/destination-actions/src/destinations/braze/index.ts index 85b600ee994..571fd40957d 100644 --- a/packages/destination-actions/src/destinations/braze/index.ts +++ b/packages/destination-actions/src/destinations/braze/index.ts @@ -101,7 +101,7 @@ const destination: DestinationDefinition = { presets: [ { name: 'Track Calls', - subscribe: 'type = "track" and event != "Order Completed" and event != "Checkout Started" and event != "Order Refunded" and event != "Order Cancelled" and event != "Product Viewed"', + subscribe: 'type = "track" and event != "Order Completed" and event != "Checkout Started" and event != "Order Refunded" and event != "Order Cancelled" and event != "Product Viewed" and event != "Product Added" and event != "Product Removed"', partnerAction: 'trackEvent', mapping: defaultValues(trackEvent.fields), type: 'automatic' @@ -162,12 +162,56 @@ const destination: DestinationDefinition = { name: 'Product Viewed (beta)', subscribe: 'event = "Product Viewed"', partnerAction: 'ecommerceSingleProduct', - mapping: { + mapping: { ...defaultValues(ecommerceSingleProduct.fields), name: EVENT_NAMES.PRODUCT_VIEWED }, type: 'automatic' }, + { + name: 'Product Added (beta)', + subscribe: 'event = "Product Added"', + partnerAction: 'ecommerce', + mapping: { + ...defaultValues(ecommerce.fields), + name: EVENT_NAMES.CART_UPDATED, + action: 'add', + products: [ + { + product_id: { '@path': '$.properties.product_id' }, + product_name: { '@path': '$.properties.name' }, + variant_id: { '@path': '$.properties.variant' }, + image_url: { '@path': '$.properties.image_url' }, + product_url: { '@path': '$.properties.url' }, + quantity: { '@path': '$.properties.quantity' }, + price: { '@path': '$.properties.price' } + } + ] + }, + type: 'automatic' + }, + { + name: 'Product Removed (beta)', + subscribe: 'event = "Product Removed"', + partnerAction: 'ecommerce', + mapping: { + ...defaultValues(ecommerce.fields), + name: EVENT_NAMES.CART_UPDATED, + action: 'remove', + products: [ + { + product_id: { '@path': '$.properties.product_id' }, + product_name: { '@path': '$.properties.name' }, + variant_id: { '@path': '$.properties.variant' }, + image_url: { '@path': '$.properties.image_url' }, + product_url: { '@path': '$.properties.url' }, + quantity: { '@path': '$.properties.quantity' }, + price: { '@path': '$.properties.price' } + } + ] + }, + type: 'automatic' + }, { name: 'Identify Calls', subscribe: 'type = "identify"', From ca1098197a5439e2c1cc525c336bf77383432e74 Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Mon, 10 Aug 2026 14:33:34 +0100 Subject: [PATCH 8/9] STRATCONN-6824 - fix checkout_started multistatus fixture after main merge The merge of main into this branch added subtotal_value/tax/shipping to the shared test payload+mapping but left the checkout_started expected event in the 'no SyncMode' multistatus test without them, causing a Nock no-match failure. Add the fields to that fixture and regenerate the ecommerce snapshots. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../__snapshots__/snapshot.test.ts.snap | 18 +++++++----------- .../braze/ecommerce/__tests__/index.test.ts | 5 ++++- .../__snapshots__/snapshot.test.ts.snap | 3 +++ 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/__tests__/__snapshots__/snapshot.test.ts.snap b/packages/destination-actions/src/destinations/braze/ecommerce/__tests__/__snapshots__/snapshot.test.ts.snap index b289febcd40..da92733b015 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/__tests__/__snapshots__/snapshot.test.ts.snap +++ b/packages/destination-actions/src/destinations/braze/ecommerce/__tests__/__snapshots__/snapshot.test.ts.snap @@ -9,21 +9,15 @@ Object { "braze_id": "tZlmdH(v3%S", "email": "rabavori@fo.sx", "external_id": "tZlmdH(v3%S", - "name": "ecommerce.order_placed", + "name": "ecommerce.checkout_started", "phone": "tZlmdH(v3%S", "properties": Object { "cart_id": "tZlmdH(v3%S", + "checkout_id": "tZlmdH(v3%S", "currency": "HTG", - "discounts": Array [ - Object { - "amount": -17122291498352.64, - "code": "tZlmdH(v3%S", - }, - ], "metadata": Object { "testType": "tZlmdH(v3%S", }, - "order_id": "tZlmdH(v3%S", "products": Array [ Object { "image_url": "http://vaci.va/inu", @@ -35,8 +29,10 @@ Object { "variant_id": "tZlmdH(v3%S", }, ], + "shipping": -17122291498352.64, "source": "tZlmdH(v3%S", - "total_discounts": -17122291498352.64, + "subtotal_value": -17122291498352.64, + "tax": -17122291498352.64, "total_value": -17122291498352.64, }, "time": "2025-01-01T00:00:00.000Z", @@ -56,11 +52,11 @@ Object { "app_id": "tZlmdH(v3%S", "braze_id": "tZlmdH(v3%S", "external_id": "tZlmdH(v3%S", - "name": "ecommerce.order_placed", + "name": "ecommerce.checkout_started", "properties": Object { "cart_id": "tZlmdH(v3%S", + "checkout_id": "tZlmdH(v3%S", "currency": "HTG", - "order_id": "tZlmdH(v3%S", "products": Array [ Object { "price": -17122291498352.64, diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/__tests__/index.test.ts b/packages/destination-actions/src/destinations/braze/ecommerce/__tests__/index.test.ts index 97b9c1be2bf..81af67b533e 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/__tests__/index.test.ts @@ -1428,7 +1428,10 @@ describe('Braze.ecommerce', () => { ], total_value: 100, checkout_id: 'checkout_id_1', - cart_id: 'cart_id_1' + cart_id: 'cart_id_1', + subtotal_value: 85, + tax: 9, + shipping: 6 }, _update_existing_only: true }, diff --git a/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/__tests__/__snapshots__/snapshot.test.ts.snap b/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/__tests__/__snapshots__/snapshot.test.ts.snap index 4b214ce5505..186cf114a61 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/__tests__/__snapshots__/snapshot.test.ts.snap +++ b/packages/destination-actions/src/destinations/braze/ecommerceSingleProduct/__tests__/__snapshots__/snapshot.test.ts.snap @@ -22,6 +22,9 @@ Object { "product_name": "Q#t9o", "product_url": "http://egu.ci/ritpaz", "source": "Q#t9o", + "type": Array [ + "price_drop", + ], "variant_id": "Q#t9o", }, "time": Any, From f8c48dfa8bafa8b30e1af9d954280711a681905a Mon Sep 17 00:00:00 2001 From: Joe Ayoub Date: Mon, 10 Aug 2026 15:07:48 +0100 Subject: [PATCH 9/9] STRATCONN-6824 - [Braze] regenerate metadata.json for ecommerce cart_updated Regenerates the destination manifest to include the new ecommerce.cart_updated event choice and the action/subtotal_value/tax/shipping fields, keeping the manifest in sync with the field definitions. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/destinations/braze/metadata.json | 877 +++++++++++++++++- 1 file changed, 847 insertions(+), 30 deletions(-) diff --git a/packages/destination-actions/src/destinations/braze/metadata.json b/packages/destination-actions/src/destinations/braze/metadata.json index 54f209f287d..ebf3491539b 100644 --- a/packages/destination-actions/src/destinations/braze/metadata.json +++ b/packages/destination-actions/src/destinations/braze/metadata.json @@ -6003,6 +6003,10 @@ "label": "Product Viewed", "value": "ecommerce.product_viewed" }, + { + "label": "Cart Updated", + "value": "ecommerce.cart_updated" + }, { "label": "Checkout Started", "value": "ecommerce.checkout_started" @@ -6419,9 +6423,18 @@ }, "cart_id": { "label": "Cart ID", - "description": "Unique identifier for the cart. If no value is passed, Braze will determine a default value (shared across cart, checkout, and order events) for the user cart mapping.", + "description": "Unique identifier for the cart. Required for cart_updated. For checkout and order events, if no value is passed, Braze will determine a default value for the user cart mapping.", "type": "string", - "required": false, + "required": { + "match": "any", + "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.cart_updated" + } + ] + }, "multiple": false, "allowNull": false, "dynamic": false, @@ -6435,6 +6448,11 @@ "depends_on": { "match": "any", "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.cart_updated" + }, { "fieldKey": "name", "operator": "is", @@ -6464,6 +6482,11 @@ "required": { "match": "any", "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.cart_updated" + }, { "fieldKey": "name", "operator": "is", @@ -6499,6 +6522,11 @@ "depends_on": { "match": "any", "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.cart_updated" + }, { "fieldKey": "name", "operator": "is", @@ -6531,6 +6559,204 @@ "format": null, "additionalProperties": false }, + "action": { + "label": "Action", + "description": "The cart action that was performed (add, remove, or replace).", + "type": "string", + "required": false, + "multiple": false, + "allowNull": false, + "dynamic": false, + "default": { + "@path": "$.properties.action" + }, + "choices": [ + { + "label": "Add", + "value": "add" + }, + { + "label": "Remove", + "value": "remove" + }, + { + "label": "Replace", + "value": "replace" + } + ], + "placeholder": null, + "properties": null, + "category": null, + "depends_on": { + "match": "any", + "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.cart_updated" + } + ] + }, + "readOnly": null, + "hidden": null, + "minimum": null, + "maximum": null, + "defaultObjectUI": null, + "disabledInputMethods": null, + "displayMode": null, + "format": null, + "additionalProperties": false + }, + "subtotal_value": { + "label": "Subtotal Value", + "description": "Subtotal monetary value of the cart before tax and shipping.", + "type": "number", + "required": false, + "multiple": false, + "allowNull": false, + "dynamic": false, + "default": { + "@path": "$.properties.subtotal" + }, + "choices": null, + "placeholder": null, + "properties": null, + "category": null, + "depends_on": { + "match": "any", + "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.cart_updated" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.checkout_started" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.order_placed" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.order_cancelled" + } + ] + }, + "readOnly": null, + "hidden": null, + "minimum": null, + "maximum": null, + "defaultObjectUI": null, + "disabledInputMethods": null, + "displayMode": null, + "format": null, + "additionalProperties": false + }, + "tax": { + "label": "Tax", + "description": "Tax amount applied to the transaction.", + "type": "number", + "required": false, + "multiple": false, + "allowNull": false, + "dynamic": false, + "default": { + "@path": "$.properties.tax" + }, + "choices": null, + "placeholder": null, + "properties": null, + "category": null, + "depends_on": { + "match": "any", + "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.cart_updated" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.checkout_started" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.order_placed" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.order_cancelled" + } + ] + }, + "readOnly": null, + "hidden": null, + "minimum": null, + "maximum": null, + "defaultObjectUI": null, + "disabledInputMethods": null, + "displayMode": null, + "format": null, + "additionalProperties": false + }, + "shipping": { + "label": "Shipping", + "description": "Shipping cost for the transaction.", + "type": "number", + "required": false, + "multiple": false, + "allowNull": false, + "dynamic": false, + "default": { + "@path": "$.properties.shipping" + }, + "choices": null, + "placeholder": null, + "properties": null, + "category": null, + "depends_on": { + "match": "any", + "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.cart_updated" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.checkout_started" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.order_placed" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.order_cancelled" + } + ] + }, + "readOnly": null, + "hidden": null, + "minimum": null, + "maximum": null, + "defaultObjectUI": null, + "disabledInputMethods": null, + "displayMode": null, + "format": null, + "additionalProperties": false + }, "total_discounts": { "label": "Total Discounts", "description": "Total amount of discounts applied to the order.", @@ -7740,6 +7966,10 @@ "label": "Product Viewed", "value": "ecommerce.product_viewed" }, + { + "label": "Cart Updated", + "value": "ecommerce.cart_updated" + }, { "label": "Checkout Started", "value": "ecommerce.checkout_started" @@ -8156,9 +8386,18 @@ }, "cart_id": { "label": "Cart ID", - "description": "Unique identifier for the cart. If no value is passed, Braze will determine a default value (shared across cart, checkout, and order events) for the user cart mapping.", + "description": "Unique identifier for the cart. Required for cart_updated. For checkout and order events, if no value is passed, Braze will determine a default value for the user cart mapping.", "type": "string", - "required": false, + "required": { + "match": "any", + "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.cart_updated" + } + ] + }, "multiple": false, "allowNull": false, "dynamic": false, @@ -8172,6 +8411,11 @@ "depends_on": { "match": "any", "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.cart_updated" + }, { "fieldKey": "name", "operator": "is", @@ -8201,6 +8445,11 @@ "required": { "match": "any", "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.cart_updated" + }, { "fieldKey": "name", "operator": "is", @@ -8236,6 +8485,11 @@ "depends_on": { "match": "any", "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.cart_updated" + }, { "fieldKey": "name", "operator": "is", @@ -8268,18 +8522,31 @@ "format": null, "additionalProperties": false }, - "total_discounts": { - "label": "Total Discounts", - "description": "Total amount of discounts applied to the order.", - "type": "number", + "action": { + "label": "Action", + "description": "The cart action that was performed (add, remove, or replace).", + "type": "string", "required": false, "multiple": false, "allowNull": false, "dynamic": false, "default": { - "@path": "$.properties.discount" + "@path": "$.properties.action" }, - "choices": null, + "choices": [ + { + "label": "Add", + "value": "add" + }, + { + "label": "Remove", + "value": "remove" + }, + { + "label": "Replace", + "value": "replace" + } + ], "placeholder": null, "properties": null, "category": null, @@ -8289,17 +8556,7 @@ { "fieldKey": "name", "operator": "is", - "value": "ecommerce.order_placed" - }, - { - "fieldKey": "name", - "operator": "is", - "value": "ecommerce.order_cancelled" - }, - { - "fieldKey": "name", - "operator": "is", - "value": "ecommerce.order_refunded" + "value": "ecommerce.cart_updated" } ] }, @@ -8313,18 +8570,213 @@ "format": null, "additionalProperties": false }, - "discounts": { - "label": "Discounts", - "description": "Details of all discounts applied to the order.", - "type": "object", + "subtotal_value": { + "label": "Subtotal Value", + "description": "Subtotal monetary value of the cart before tax and shipping.", + "type": "number", "required": false, - "multiple": true, + "multiple": false, "allowNull": false, "dynamic": false, "default": { - "@arrayPath": [ - "$.properties.discount_items", - { + "@path": "$.properties.subtotal" + }, + "choices": null, + "placeholder": null, + "properties": null, + "category": null, + "depends_on": { + "match": "any", + "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.cart_updated" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.checkout_started" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.order_placed" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.order_cancelled" + } + ] + }, + "readOnly": null, + "hidden": null, + "minimum": null, + "maximum": null, + "defaultObjectUI": null, + "disabledInputMethods": null, + "displayMode": null, + "format": null, + "additionalProperties": false + }, + "tax": { + "label": "Tax", + "description": "Tax amount applied to the transaction.", + "type": "number", + "required": false, + "multiple": false, + "allowNull": false, + "dynamic": false, + "default": { + "@path": "$.properties.tax" + }, + "choices": null, + "placeholder": null, + "properties": null, + "category": null, + "depends_on": { + "match": "any", + "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.cart_updated" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.checkout_started" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.order_placed" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.order_cancelled" + } + ] + }, + "readOnly": null, + "hidden": null, + "minimum": null, + "maximum": null, + "defaultObjectUI": null, + "disabledInputMethods": null, + "displayMode": null, + "format": null, + "additionalProperties": false + }, + "shipping": { + "label": "Shipping", + "description": "Shipping cost for the transaction.", + "type": "number", + "required": false, + "multiple": false, + "allowNull": false, + "dynamic": false, + "default": { + "@path": "$.properties.shipping" + }, + "choices": null, + "placeholder": null, + "properties": null, + "category": null, + "depends_on": { + "match": "any", + "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.cart_updated" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.checkout_started" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.order_placed" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.order_cancelled" + } + ] + }, + "readOnly": null, + "hidden": null, + "minimum": null, + "maximum": null, + "defaultObjectUI": null, + "disabledInputMethods": null, + "displayMode": null, + "format": null, + "additionalProperties": false + }, + "total_discounts": { + "label": "Total Discounts", + "description": "Total amount of discounts applied to the order.", + "type": "number", + "required": false, + "multiple": false, + "allowNull": false, + "dynamic": false, + "default": { + "@path": "$.properties.discount" + }, + "choices": null, + "placeholder": null, + "properties": null, + "category": null, + "depends_on": { + "match": "any", + "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.order_placed" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.order_cancelled" + }, + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.order_refunded" + } + ] + }, + "readOnly": null, + "hidden": null, + "minimum": null, + "maximum": null, + "defaultObjectUI": null, + "disabledInputMethods": null, + "displayMode": null, + "format": null, + "additionalProperties": false + }, + "discounts": { + "label": "Discounts", + "description": "Details of all discounts applied to the order.", + "type": "object", + "required": false, + "multiple": true, + "allowNull": false, + "dynamic": false, + "default": { + "@arrayPath": [ + "$.properties.discount_items", + { "code": { "@path": "$.code" }, @@ -9216,6 +9668,50 @@ "format": null, "additionalProperties": false }, + "catalog_type": { + "label": "Catalog Trigger Type", + "description": "Required to use Braze catalog trigger features. Accepted values: price_drop, back_in_stock.", + "type": "string", + "required": false, + "multiple": true, + "allowNull": false, + "dynamic": false, + "default": { + "@path": "$.properties.type" + }, + "choices": [ + { + "label": "Price Drop", + "value": "price_drop" + }, + { + "label": "Back In Stock", + "value": "back_in_stock" + } + ], + "placeholder": null, + "properties": null, + "category": null, + "depends_on": { + "match": "any", + "conditions": [ + { + "fieldKey": "name", + "operator": "is", + "value": "ecommerce.product_viewed" + } + ] + }, + "readOnly": null, + "hidden": null, + "minimum": null, + "maximum": null, + "defaultObjectUI": null, + "disabledInputMethods": null, + "displayMode": null, + "format": null, + "additionalProperties": false + }, "product": { "label": "Product", "description": "Product details associated with the ecommerce event.", @@ -9424,7 +9920,7 @@ "name": "Track Calls", "type": "automatic", "partnerAction": "trackEvent", - "subscribe": "type = \"track\" and event != \"Order Completed\" and event != \"Checkout Started\" and event != \"Order Refunded\" and event != \"Order Cancelled\" and event != \"Product Viewed\"", + "subscribe": "type = \"track\" and event != \"Order Completed\" and event != \"Checkout Started\" and event != \"Order Refunded\" and event != \"Order Cancelled\" and event != \"Product Viewed\" and event != \"Product Added\" and event != \"Product Removed\"", "mapping": { "external_id": { "@path": "$.userId" @@ -9526,6 +10022,18 @@ "total_value": { "@path": "$.properties.total" }, + "action": { + "@path": "$.properties.action" + }, + "subtotal_value": { + "@path": "$.properties.subtotal" + }, + "tax": { + "@path": "$.properties.tax" + }, + "shipping": { + "@path": "$.properties.shipping" + }, "total_discounts": { "@path": "$.properties.discount" }, @@ -9653,6 +10161,18 @@ "total_value": { "@path": "$.properties.total" }, + "action": { + "@path": "$.properties.action" + }, + "subtotal_value": { + "@path": "$.properties.subtotal" + }, + "tax": { + "@path": "$.properties.tax" + }, + "shipping": { + "@path": "$.properties.shipping" + }, "total_discounts": { "@path": "$.properties.discount" }, @@ -9780,6 +10300,18 @@ "total_value": { "@path": "$.properties.total" }, + "action": { + "@path": "$.properties.action" + }, + "subtotal_value": { + "@path": "$.properties.subtotal" + }, + "tax": { + "@path": "$.properties.tax" + }, + "shipping": { + "@path": "$.properties.shipping" + }, "total_discounts": { "@path": "$.properties.discount" }, @@ -9907,6 +10439,18 @@ "total_value": { "@path": "$.properties.total" }, + "action": { + "@path": "$.properties.action" + }, + "subtotal_value": { + "@path": "$.properties.subtotal" + }, + "tax": { + "@path": "$.properties.tax" + }, + "shipping": { + "@path": "$.properties.shipping" + }, "total_discounts": { "@path": "$.properties.discount" }, @@ -10034,6 +10578,18 @@ "total_value": { "@path": "$.properties.total" }, + "action": { + "@path": "$.properties.action" + }, + "subtotal_value": { + "@path": "$.properties.subtotal" + }, + "tax": { + "@path": "$.properties.tax" + }, + "shipping": { + "@path": "$.properties.shipping" + }, "total_discounts": { "@path": "$.properties.discount" }, @@ -10058,6 +10614,9 @@ }, "enable_batching": true, "batch_size": 75, + "catalog_type": { + "@path": "$.properties.type" + }, "product": { "product_id": { "@path": "$.properties.product_id" @@ -10082,6 +10641,264 @@ }, "eventSlug": null }, + { + "name": "Product Added (beta)", + "type": "automatic", + "partnerAction": "ecommerce", + "subscribe": "event = \"Product Added\"", + "mapping": { + "external_id": { + "@path": "$.userId" + }, + "email": { + "@if": { + "exists": { + "@path": "$.context.traits.email" + }, + "then": { + "@path": "$.context.traits.email" + }, + "else": { + "@path": "$.properties.email" + } + } + }, + "phone": { + "@if": { + "exists": { + "@path": "$.context.traits.phone" + }, + "then": { + "@path": "$.context.traits.phone" + }, + "else": { + "@path": "$.properties.phone" + } + } + }, + "braze_id": { + "@if": { + "exists": { + "@path": "$.context.traits.braze_id" + }, + "then": { + "@path": "$.context.traits.braze_id" + }, + "else": { + "@path": "$.properties.braze_id" + } + } + }, + "cancel_reason": { + "@path": "$.properties.reason" + }, + "time": { + "@path": "$.timestamp" + }, + "checkout_id": { + "@path": "$.properties.checkout_id" + }, + "order_id": { + "@path": "$.properties.order_id" + }, + "cart_id": { + "@path": "$.properties.cart_id" + }, + "total_value": { + "@path": "$.properties.total" + }, + "action": "add", + "subtotal_value": { + "@path": "$.properties.subtotal" + }, + "tax": { + "@path": "$.properties.tax" + }, + "shipping": { + "@path": "$.properties.shipping" + }, + "total_discounts": { + "@path": "$.properties.discount" + }, + "discounts": { + "@arrayPath": [ + "$.properties.discount_items", + { + "code": { + "@path": "$.code" + }, + "amount": { + "@path": "$.amount" + } + } + ] + }, + "currency": { + "@path": "$.properties.currency" + }, + "source": { + "@path": "$.properties.source" + }, + "enable_batching": true, + "batch_size": 75, + "products": [ + { + "product_id": { + "@path": "$.properties.product_id" + }, + "product_name": { + "@path": "$.properties.name" + }, + "variant_id": { + "@path": "$.properties.variant" + }, + "image_url": { + "@path": "$.properties.image_url" + }, + "product_url": { + "@path": "$.properties.url" + }, + "quantity": { + "@path": "$.properties.quantity" + }, + "price": { + "@path": "$.properties.price" + } + } + ], + "name": "ecommerce.cart_updated" + }, + "eventSlug": null + }, + { + "name": "Product Removed (beta)", + "type": "automatic", + "partnerAction": "ecommerce", + "subscribe": "event = \"Product Removed\"", + "mapping": { + "external_id": { + "@path": "$.userId" + }, + "email": { + "@if": { + "exists": { + "@path": "$.context.traits.email" + }, + "then": { + "@path": "$.context.traits.email" + }, + "else": { + "@path": "$.properties.email" + } + } + }, + "phone": { + "@if": { + "exists": { + "@path": "$.context.traits.phone" + }, + "then": { + "@path": "$.context.traits.phone" + }, + "else": { + "@path": "$.properties.phone" + } + } + }, + "braze_id": { + "@if": { + "exists": { + "@path": "$.context.traits.braze_id" + }, + "then": { + "@path": "$.context.traits.braze_id" + }, + "else": { + "@path": "$.properties.braze_id" + } + } + }, + "cancel_reason": { + "@path": "$.properties.reason" + }, + "time": { + "@path": "$.timestamp" + }, + "checkout_id": { + "@path": "$.properties.checkout_id" + }, + "order_id": { + "@path": "$.properties.order_id" + }, + "cart_id": { + "@path": "$.properties.cart_id" + }, + "total_value": { + "@path": "$.properties.total" + }, + "action": "remove", + "subtotal_value": { + "@path": "$.properties.subtotal" + }, + "tax": { + "@path": "$.properties.tax" + }, + "shipping": { + "@path": "$.properties.shipping" + }, + "total_discounts": { + "@path": "$.properties.discount" + }, + "discounts": { + "@arrayPath": [ + "$.properties.discount_items", + { + "code": { + "@path": "$.code" + }, + "amount": { + "@path": "$.amount" + } + } + ] + }, + "currency": { + "@path": "$.properties.currency" + }, + "source": { + "@path": "$.properties.source" + }, + "enable_batching": true, + "batch_size": 75, + "products": [ + { + "product_id": { + "@path": "$.properties.product_id" + }, + "product_name": { + "@path": "$.properties.name" + }, + "variant_id": { + "@path": "$.properties.variant" + }, + "image_url": { + "@path": "$.properties.image_url" + }, + "product_url": { + "@path": "$.properties.url" + }, + "quantity": { + "@path": "$.properties.quantity" + }, + "price": { + "@path": "$.properties.price" + } + } + ], + "name": "ecommerce.cart_updated" + }, + "eventSlug": null + }, { "name": "Identify Calls", "type": "automatic",