From 8e1f40bfc07313c55b3f565a77a490eaa77b99bd Mon Sep 17 00:00:00 2001 From: Nickolas Dimitrakas Date: Thu, 16 Jul 2026 13:42:46 -0400 Subject: [PATCH] fix(braze-42): honor replaceSkuWithProductName on recommended eCommerce path Recommended eCommerce forwarding (useEcommerceRecommendedEvents) always used product.sku as productId, while legacy logPurchase honors the replaceSkuWithProductName setting. The same commerce event could therefore identify products differently in Braze depending on which forwarding path ran. Add recommendedProductId(), mirroring the legacy flag check, and use it everywhere the recommended path emits a product identifier (line items, ProductViewedEvent, and the order_refunded JSON payload). Also removes a redundant explicit `it ->` lambda label in logEvent flagged during PR #728 review. Co-Authored-By: Claude Opus 4.8 --- .../kotlin/com/mparticle/kits/AppboyKit.kt | 18 +++++-- .../kits/RecommendedEcommerceTests.kt | 51 +++++++++++++++++++ 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/kits/braze/braze-42/src/main/kotlin/com/mparticle/kits/AppboyKit.kt b/kits/braze/braze-42/src/main/kotlin/com/mparticle/kits/AppboyKit.kt index 9dc545da9..73ffc31f5 100644 --- a/kits/braze/braze-42/src/main/kotlin/com/mparticle/kits/AppboyKit.kt +++ b/kits/braze/braze-42/src/main/kotlin/com/mparticle/kits/AppboyKit.kt @@ -190,7 +190,7 @@ open class AppboyKit : } else { val properties = BrazeProperties() val brazePropertiesSetter = BrazePropertiesSetter(properties, enableTypeDetection) - event.customAttributeStrings?.let { it -> + event.customAttributeStrings?.let { for ((key, value) in it) { newAttributes[key] = brazePropertiesSetter.parseValue(key, value) } @@ -202,7 +202,7 @@ open class AppboyKit : object : IValueCallback { override fun onSuccess(value: BrazeUser) { val userAttributeSetter = UserAttributeSetter(value, enableTypeDetection) - event.customAttributeStrings?.let { it -> + event.customAttributeStrings?.let { for ((key, attributeValue) in it) { val hashedKey = KitUtils.hashForFiltering(event.eventType.value.toString() + event.eventName + key) @@ -1018,7 +1018,7 @@ open class AppboyKit : for (product in products) { braze.logEcommerceEvent( ProductViewedEvent( - productId = product.sku, + productId = recommendedProductId(product), productName = product.name, variantId = recommendedVariantId(product), price = product.unitPrice, @@ -1100,6 +1100,14 @@ open class AppboyKit : return if (variant.isNullOrEmpty()) product.sku else variant } + private fun recommendedProductId(product: Product): String = + try { + if (settings[REPLACE_SKU_AS_PRODUCT_NAME] == "True") product.name else product.sku + } catch (e: Exception) { + Logger.error(e, "The Braze kit threw an exception while searching for forward sku as product name flag.") + product.sku + } + private fun recommendedImageUrl(product: Product): String? = recommendedProductAttribute(product, IMAGE_URL_ATTRIBUTES) private fun recommendedProductUrl(product: Product): String? = recommendedProductAttribute(product, PRODUCT_URL_ATTRIBUTES) @@ -1136,7 +1144,7 @@ open class AppboyKit : private fun recommendedLineItems(products: List): List = products.map { product -> EcommerceProduct( - productId = product.sku, + productId = recommendedProductId(product), productName = product.name, variantId = recommendedVariantId(product), price = product.unitPrice, @@ -1151,7 +1159,7 @@ open class AppboyKit : val array = JSONArray() for (product in products) { val obj = JSONObject() - obj.put(RECOMMENDED_PRODUCT_ID_KEY, product.sku) + obj.put(RECOMMENDED_PRODUCT_ID_KEY, recommendedProductId(product)) obj.put(RECOMMENDED_PRODUCT_NAME_KEY, product.name) obj.put(RECOMMENDED_VARIANT_ID_KEY, recommendedVariantId(product)) obj.put(RECOMMENDED_QUANTITY_KEY, product.quantity.toLong().coerceAtLeast(1L)) diff --git a/kits/braze/braze-42/src/test/kotlin/com/mparticle/kits/RecommendedEcommerceTests.kt b/kits/braze/braze-42/src/test/kotlin/com/mparticle/kits/RecommendedEcommerceTests.kt index fcf18f810..7f47b8863 100644 --- a/kits/braze/braze-42/src/test/kotlin/com/mparticle/kits/RecommendedEcommerceTests.kt +++ b/kits/braze/braze-42/src/test/kotlin/com/mparticle/kits/RecommendedEcommerceTests.kt @@ -14,6 +14,8 @@ import com.mparticle.commerce.TransactionAttributes import com.mparticle.identity.IdentityApi import com.mparticle.kits.mocks.MockAppboyKit import com.mparticle.kits.mocks.MockKitConfiguration +import org.json.JSONArray +import org.json.JSONObject import org.junit.Assert import org.junit.Before import org.junit.Test @@ -58,6 +60,14 @@ class RecommendedEcommerceTests { ), ).build() + private fun kitWithReplaceSkuFlag(): MockAppboyKit { + val settings = hashMapOf(AppboyKit.REPLACE_SKU_AS_PRODUCT_NAME to "True") + return MockAppboyKit().apply { + configuration = KitConfiguration.createKitConfiguration(JSONObject().put("as", JSONObject(settings as Map<*, *>))) + useEcommerceRecommendedEvents = true + } + } + @Test fun testAddToCartLogsCartUpdatedAddEvent() { kit.logEvent( @@ -145,6 +155,33 @@ class RecommendedEcommerceTests { Assert.assertEquals("sku2", second.variantId) } + @Test + fun testViewDetailHonorsReplaceSkuWithProductNameFlag() { + kitWithReplaceSkuFlag().logEvent( + CommerceEvent + .Builder(Product.DETAIL, productWithUrls()) + .currency("USD") + .build(), + ) + Assert.assertEquals(1, Braze.ecommerceEvents.size) + val event = Braze.ecommerceEvents[0] as ProductViewedEvent + Assert.assertEquals("product name", event.productId) + Assert.assertEquals("product name", event.productName) + } + + @Test + fun testLineItemsHonorReplaceSkuWithProductNameFlag() { + kitWithReplaceSkuFlag().logEvent( + CommerceEvent + .Builder(Product.ADD_TO_CART, productWithUrls()) + .currency("USD") + .build(), + ) + Assert.assertEquals(1, Braze.ecommerceEvents.size) + val event = Braze.ecommerceEvents[0] as CartUpdatedEvent + Assert.assertEquals("product name", event.products[0].productId) + } + @Test fun testPurchaseLogsOrderPlacedEvent() { val transactionAttributes = @@ -196,6 +233,20 @@ class RecommendedEcommerceTests { Assert.assertNotNull(refund?.properties?.get("products")) } + @Test + fun testRefundProductsJsonHonorsReplaceSkuWithProductNameFlag() { + kitWithReplaceSkuFlag().logEvent( + CommerceEvent + .Builder(Product.REFUND, productWithUrls()) + .currency("USD") + .transactionAttributes(TransactionAttributes("order-42").setRevenue(99.0)) + .build(), + ) + val refund = Braze.events["ecommerce.order_refunded"] + val products = refund?.properties?.get("products") as JSONArray + Assert.assertEquals("product name", products.getJSONObject(0).getString("product_id")) + } + @Test fun testToggleOffFallsBackToLegacyPurchase() { val kit =