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 da041492693..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 @@ -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: [ { @@ -99,25 +103,29 @@ 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' }, source: { '@path': '$.properties.source' }, products: { - '@arrayPath': [ - '$.properties.products', - { - product_id: { '@path': '$.product_id' }, - product_name: { '@path': '$.name' }, - variant_id: { '@path': '$.variant' }, - image_url: { '@path': '$.image_url' }, - product_url: { '@path': '$.url' }, - quantity: { '@path': '$.quantity' }, - price: { '@path': '$.price' }, - color: { '@path': '$.color' }, - size: { '@path': '$.size' } - } - ] + '@arrayPath': [ + '$.properties.products', + { + product_id: { '@path': '$.product_id' }, + product_name: { '@path': '$.name' }, + variant_id: { '@path': '$.variant'}, + image_url: {'@path': '$.image_url'}, + product_url: {'@path': '$.url'}, + quantity: {'@path': '$.quantity'}, + price: {'@path': '$.price'}, + color: { '@path': '$.color' }, + size: { '@path': '$.size' } + } + ] }, product: { product_id: { '@path': '$.properties.product.product_id' }, @@ -191,13 +199,16 @@ describe('Braze.ecommerce', () => { } ], total_value: 100, - order_id: 'order_id_1', + 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, @@ -228,6 +239,7 @@ describe('Braze.ecommerce', () => { }) it('should send Checkout Started event correctly', async () => { + const mapping2 = { ...mapping, name: EVENT_NAMES.CHECKOUT_STARTED @@ -277,8 +289,11 @@ describe('Braze.ecommerce', () => { } ], total_value: 100, - checkout_id: 'checkout_id_1', - cart_id: 'cart_id_1', + 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, @@ -390,6 +405,7 @@ describe('Braze.ecommerce', () => { }) it('should send Order Cancelled event correctly', async () => { + const mapping2 = { ...mapping, name: EVENT_NAMES.ORDER_CANCELLED @@ -441,6 +457,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 }, @@ -473,6 +492,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)) const e = createTestEvent(deepCopy) @@ -519,16 +712,18 @@ 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 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 e5 = createTestEvent({...deepCopy5, userId: 'userId5', event: 'ecommerce.cart_updated' }) + const events = [e1, e2, e3, e4, e5] const mapping2 = { ...mapping, - name: { '@path': '$.event' } + name: { '@path': '$.event' }, } const json = { @@ -574,13 +769,16 @@ describe('Braze.ecommerce', () => { } ], total_value: 100, - order_id: 'order_id_1', + 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' + { code: "SUMMER21", amount: 5 }, + { code: "VIPCUSTOMER", amount: 5 } + ] }, _update_existing_only: true }, @@ -675,8 +873,11 @@ describe('Braze.ecommerce', () => { } ], total_value: 100, - checkout_id: 'checkout_id_1', - cart_id: 'cart_id_1' + checkout_id: "checkout_id_1", + cart_id: "cart_id_1", + subtotal_value: 85, + tax: 9, + shipping: 6 }, _update_existing_only: true }, @@ -723,6 +924,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 }, @@ -730,6 +934,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 } ] } @@ -743,6 +996,8 @@ describe('Braze.ecommerce', () => { }) expect(response.length).toBe(1) + + expect(response.length).toBe(1) }) it('should return correct multistatus response if there is a bad event', async () => { @@ -860,8 +1115,11 @@ describe('Braze.ecommerce', () => { } ], total_value: 100, - checkout_id: 'checkout_id_1', - cart_id: 'cart_id_1' + checkout_id: "checkout_id_1", + cart_id: "cart_id_1", + subtotal_value: 85, + tax: 9, + shipping: 6 }, _update_existing_only: true }, @@ -908,6 +1166,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 }, @@ -1167,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 }, @@ -1214,6 +1478,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 }, diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/constants.ts b/packages/destination-actions/src/destinations/braze/ecommerce/constants.ts index bb300fc0c1d..317938b128b 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/fields.ts b/packages/destination-actions/src/destinations/braze/ecommerce/fields.ts index 541969ebbf0..e3b1aa4d805 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 }, @@ -198,27 +198,27 @@ 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] - // } - // ] - // }, + 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 +241,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 +271,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 +300,128 @@ 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 + } + ] + } +} + +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.', @@ -527,6 +649,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', @@ -558,6 +703,10 @@ export const commonFields = { order_id, cart_id, total_value, + action, + subtotal_value, + tax, + shipping, total_discounts, discounts, currency, @@ -565,4 +714,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 639d0d439cc..97e69177b2a 100644 --- a/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts +++ b/packages/destination-actions/src/destinations/braze/ecommerce/functions.ts @@ -13,7 +13,7 @@ import type { ProductViewedEventName, ProductViewedEvent, MultiProductBaseEvent, - //CartUpdatedEvent, + CartUpdatedEvent, CheckoutStartedEvent, OrderPlacedEvent, OrderRefundedEvent, @@ -154,19 +154,23 @@ function getJSONItem( switch (name) { case EVENT_NAMES.PRODUCT_VIEWED: { - const { product } = payload as SingleProductPayload + const { + product, + catalog_type + } = payload as SingleProductPayload const event: ProductViewedEvent = { ...baseEvent, name: EVENT_NAMES.PRODUCT_VIEWED, properties: { ...baseEvent.properties, - ...product + ...product, + ...(catalog_type && catalog_type.length > 0 ? { type: catalog_type } : {}) } } 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: @@ -183,23 +187,33 @@ function getJSONItem( } } - switch (name) { - // 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 - // } + switch(name) { + case EVENT_NAMES.CART_UPDATED: { + 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, + ...(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, @@ -207,14 +221,25 @@ function getJSONItem( 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 { order_id, cart_id, total_discounts, discounts } = payload + const { + order_id, + cart_id, + subtotal_value, + tax, + shipping, + total_discounts, + discounts + } = payload as Payload const event: OrderPlacedEvent = { ...multiProductEvent, @@ -222,16 +247,23 @@ function getJSONItem( 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 { order_id, total_discounts, discounts } = payload + const { + order_id, + total_discounts, + discounts + } = payload as Payload const event: OrderRefundedEvent = { ...multiProductEvent, @@ -247,7 +279,15 @@ function getJSONItem( } case EVENT_NAMES.ORDER_CANCELLED: { - const { order_id, cancel_reason, total_discounts, discounts } = payload + const { + order_id, + cancel_reason, + subtotal_value, + tax, + shipping, + total_discounts, + discounts + } = payload as Payload const event: OrderCancelledEvent = { ...multiProductEvent, @@ -256,6 +296,9 @@ function getJSONItem( ...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..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,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. */ diff --git a/packages/destination-actions/src/destinations/braze/ecommerce/types.ts b/packages/destination-actions/src/destinations/braze/ecommerce/types.ts index 952289943f6..1ed06b1fe3a 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 @@ -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 { @@ -85,21 +87,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 +117,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 @@ -138,6 +150,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<{ 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, 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 95298236d1c..0058a0a495e 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 @@ -152,6 +152,75 @@ describe('Braze.ecommerce', () => { expect(response.length).toBe(1) }) + 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) + }) + it('should default syncMode to add if missing', async () => { nock(settings.endpoint).post('/users/track').reply(200) 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..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. */ @@ -85,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. */ 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: { 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"', 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",