Skip to content

STRATCONN-6824 - [Braze] - ecommerce.cart_updated support - #3804

Open
joe-ayoub-segment wants to merge 7 commits into
mainfrom
braze-ecommerce-addtocart
Open

STRATCONN-6824 - [Braze] - ecommerce.cart_updated support#3804
joe-ayoub-segment wants to merge 7 commits into
mainfrom
braze-ecommerce-addtocart

Conversation

@joe-ayoub-segment

@joe-ayoub-segment joe-ayoub-segment commented May 21, 2026

Copy link
Copy Markdown
Contributor

Adding support to Braze destination for Ecommerce ecommerce.cart_updated events.
https://twilio-engineering.atlassian.net/browse/STRATCONN-6824

Also adds new presets for Product Added and Product Removed.

Testing

New unit tests:

  • Cart Updated with all fields (action, subtotal_value, tax, shipping, cart_id)
  • Cart Updated with minimal fields (just cart_id)
  • Cart Updated in the batch test (5th event alongside existing 4)
  • Product Viewed with catalog_type: ['price_drop', 'back_in_stock']

Updated unit tests:

  • Order Placed — expected JSON now includes subtotal_value, tax, shipping
  • Checkout Started — expected JSON now includes subtotal_value, tax, shipping
  • Order Cancelled — expected JSON now includes subtotal_value, tax, shipping
  • Batch test — all events updated with new fields + cart_updated added
  • Both multistatus tests — sent objects and nock bodies updated with new fields

Staging Test Plan

  1. Cart Updated (replace) — Send a cart_updated event with action: 'replace', cart_id, total_value,
    subtotal_value, tax, shipping, and products. Verify Braze receives all fields.
  2. Cart Updated (add/remove) — Send a cart_updated with action: 'add' and only cart_id + products (no
    subtotal/tax/shipping). Verify optional fields are omitted cleanly.
  3. Checkout Started with new fields — Send checkout_started with subtotal_value, tax, shipping
    populated. Verify they appear in Braze payload.
  4. Product Viewed with catalog_type — Send product_viewed with catalog_type: ['price_drop',
    'back_in_stock']. Verify Braze receives type: ["price_drop", "back_in_stock"] in properties.
  5. Order Placed (batch) — Send a batch with mixed event types including cart_updated. Verify all
    events process correctly in a single request.

Security Review

Please ensure sensitive data is properly protected in your integration.

  • Reviewed all field definitions for sensitive data (API keys, tokens, passwords, client secrets) and confirmed they use type: 'password'

New Destination Checklist

  • Extracted all action API versions to verioning-info.ts file. example

Copilot AI lite review requested due to automatic review settings May 21, 2026 09:37
@joe-ayoub-segment joe-ayoub-segment self-assigned this May 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR attempts to add Braze ecommerce support for an additional event type by introducing ecommerce.cart_updated alongside existing multi-product ecommerce recommended events.

Changes:

  • Enabled the CART_UPDATED event name constant and added it into the ecommerce event unions/types.
  • Added CART_UPDATED handling in the ecommerce JSON builder (getJSONItem) to emit a Cart Updated event payload.
  • Expanded several ecommerce event TypeScript interfaces with additional optional pricing fields (e.g., subtotal_value, tax, shipping).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
packages/destination-actions/src/destinations/braze/ecommerce/types.ts Adds CartUpdatedEvent and extends ecommerce event typings/unions.
packages/destination-actions/src/destinations/braze/ecommerce/functions.ts Adds runtime JSON-building support for EVENT_NAMES.CART_UPDATED.
packages/destination-actions/src/destinations/braze/ecommerce/constants.ts Enables CART_UPDATED in the Braze ecommerce event name constants.
Comments suppressed due to low confidence (2)

packages/destination-actions/src/destinations/braze/ecommerce/constants.ts:6

  • CART_UPDATED is now defined/handled in code, but the ecommerce action’s field definitions still have the “Cart Updated” choice and related depends_on / required conditions commented out (see packages/destination-actions/src/destinations/braze/ecommerce/fields.ts). As-is, users likely can’t select/configure this event through the action schema, so the added support won’t be reachable. Please enable the choice and any required/depends_on rules and regenerate the action types as needed.
  PRODUCT_VIEWED: 'ecommerce.product_viewed',
  CHECKOUT_STARTED: 'ecommerce.checkout_started',
  CART_UPDATED: 'ecommerce.cart_updated',
  ORDER_PLACED: 'ecommerce.order_placed',
  ORDER_CANCELLED: 'ecommerce.order_cancelled',

packages/destination-actions/src/destinations/braze/ecommerce/types.ts:92

  • CartUpdatedEvent.properties.cart_id is typed as required, but the action Payload defines cart_id as optional and the field description suggests Braze can default it when omitted. Consider making cart_id optional in CartUpdatedEvent (and/or conditionally including it in the JSON properties object) to keep the TypeScript types aligned with the actual payload behavior.
export interface CartUpdatedEvent extends MultiProductBaseEvent {
    name: CartUpdatedEventName
    properties: MultiProductBaseEvent['properties'] & {
        cart_id: string
        action?: 'add' | 'remove' | 'replace'

Copilot AI review requested due to automatic review settings May 21, 2026 10:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (2)

packages/destination-actions/src/destinations/braze/ecommerce/functions.ts:208

  • In the CART_UPDATED branch, cart_id is always added to properties via a type assertion (cart_id: cart_id as string). If the input omits cart_id (currently allowed by the field schema), the outbound payload will include cart_id: undefined, which is likely to be rejected by Braze or cause inconsistent behavior. Prefer conditionally adding cart_id only when present, or enforce cart_id as required for this event.
        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 } : {})
            }

packages/destination-actions/src/destinations/braze/ecommerce/fields.ts:223

  • cart_id is included for Cart Updated via depends_on only, so it’s optional at validation time. Given the new CART_UPDATED implementation currently assumes a string cart_id, either add a required condition for name = ecommerce.cart_updated (and/or align the runtime to omit it when absent).
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.',
    type: 'string',
    default: {'@path': '$.properties.cart_id'},
    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
            }
        ]
    }

Copilot AI review requested due to automatic review settings May 21, 2026 12:18
@joe-ayoub-segment joe-ayoub-segment changed the title STRATCONN-6824 - [Braze] - add ecommerce addtocart event STRATCONN-6824 - [Braze] - ecommerce.cart_updated support May 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

packages/destination-actions/src/destinations/braze/ecommerce/fields.ts:16

  • commonFields.name is reused by both the multi-product and single-product actions, but the choice list now includes Cart Updated. The single-product action doesn’t define a products field, and send() will treat ecommerce.cart_updated as a multi-product event and call payload.products.map(...), which will throw at runtime if a user selects this option in the single-product action. Consider scoping name choices per action (e.g., override name in ecommerceSingleProduct to only allow PRODUCT_VIEWED) or add the required products field/payload shape to the single-product action if it should support cart updates.
    required: true,
    choices: [
        { label: 'Product Viewed', value: EVENT_NAMES.PRODUCT_VIEWED },
        { 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 },
        { label: 'Order Refunded', value: EVENT_NAMES.ORDER_REFUNDED }

@joe-ayoub-segment joe-ayoub-segment added the needs-stage-test Must be tested in Stage before deployment label May 21, 2026
@joe-ayoub-segment
joe-ayoub-segment marked this pull request as ready for review May 21, 2026 12:55
@joe-ayoub-segment
joe-ayoub-segment requested a review from a team as a code owner May 21, 2026 12:55
Copilot AI review requested due to automatic review settings May 21, 2026 12:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

packages/destination-actions/src/destinations/braze/ecommerce/fields.ts:16

  • commonFields.name is shared by both the multi-product ecommerce action and the single-product ecommerceSingleProduct action (see ecommerceSingleProduct/index.ts importing commonFields). Enabling CART_UPDATED here makes it selectable in the single-product action as well, but send() handles ecommerce.cart_updated as a multi-product event and will attempt to read payload.products (which the single-product payload does not have), leading to a runtime error if a user selects this event name in the single-product action. Consider scoping the name field choices per action (e.g., a separate name field for single-product that only allows PRODUCT_VIEWED), or update send() to safely handle single-product payloads for CART_UPDATED (and other multi-product events) without accessing missing fields.
    required: true,
    choices: [
        { label: 'Product Viewed', value: EVENT_NAMES.PRODUCT_VIEWED },
        { 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 },
        { label: 'Order Refunded', value: EVENT_NAMES.ORDER_REFUNDED }

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (5)

packages/destination-actions/src/destinations/braze/ecommerce/functions.ts:173

  • catalog_type is treated as an array (catalog_type.length) and then passed through as properties.type. If mappings provide a string (or other non-array), this will either send the wrong type to Braze (string instead of string[]) or behave unexpectedly. Consider guarding with Array.isArray(catalog_type) before checking length and including it, and optionally normalizing/filtering to the allowed values.
        product,
        catalog_type
      } = payload as SingleProductPayload

      const event: ProductViewedEvent = {
        ...baseEvent,
        name: EVENT_NAMES.PRODUCT_VIEWED,
        properties: {
          ...baseEvent.properties,
          ...product,
          ...(catalog_type && catalog_type.length > 0 ? { type: catalog_type } : {})
        }

packages/destination-actions/src/destinations/braze/ecommerce/fields.ts:662

  • Defaulting catalog_type from $.properties.type is risky because properties.type is a very common/overloaded attribute in ecommerce payloads (often meaning product type/category), and this could unintentionally start sending Braze catalog trigger type data for existing Product Viewed events. Consider removing the default (leave unmapped by default), or sourcing from a less ambiguous property (e.g., $.properties.catalog_type) so the feature is opt-in and doesn’t conflict with existing schemas.
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,

packages/destination-actions/src/destinations/braze/ecommerce/fields.ts:253

  • The PR description/staging plan indicates cart_updated may be sent with only cart_id + products (omitting totals). This change makes total_value required for CART_UPDATED in the UI. Either update the PR description/test plan to reflect that total_value is required for cart_updated, or relax this requirement if Braze allows cart_updated without total_value.
    required: {
        match: 'any',
        conditions: [
            {
                fieldKey: 'name',
                operator: 'is',
                value: EVENT_NAMES.CART_UPDATED
            },
            {
                fieldKey: 'name',
                operator: 'is',
                value: EVENT_NAMES.CHECKOUT_STARTED
            },

packages/destination-actions/src/destinations/braze/ecommerce/functions.ts:210

  • action is cast to 'add' | 'remove' | 'replace' without runtime validation. If a mapping provides any other string, the payload will still include it and may be rejected by Braze. Consider validating action against the allowed set before including it (or emitting a clear error) rather than relying on a type cast.
          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 } : {})
            }

packages/destination-actions/src/destinations/braze/ecommerce/types.ts:97

  • There’s trailing whitespace on the tax?: number line. This can cause avoidable lint/prettier noise in future diffs; consider trimming it.
        action?: 'add' | 'remove' | 'replace'
        subtotal_value?: number
        tax?: number 
        shipping?: number

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants