fix(product-carousel): address PR review comments

- Replace catalogId with businessOwnerJid (required for catalog reference)
- Fix cards structure to use proper IInteractiveMessage[] format
- Each card now uses collectionMessage with bizJid and id
- Fix body reading from productCarousel.body (was reading from message.body)
- Remove 'as any' type casting by using correct proto types
- Update examples in types and function documentation

Addresses:
- Schema mismatch in carousel cards
- Body text being silently ignored when nested in productCarousel
- Improper type casting masking validation errors

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Renato Alcara
2026-01-26 18:32:45 -03:00
parent 3d4fa3e007
commit 0d569752af
2 changed files with 31 additions and 26 deletions
+11 -12
View File
@@ -632,23 +632,23 @@ export type ProductCarouselCard = {
* ```typescript * ```typescript
* await sock.sendMessage(jid, { * await sock.sendMessage(jid, {
* productCarousel: { * productCarousel: {
* catalogId: '123456789', * businessOwnerJid: '5511999999999@s.whatsapp.net',
* products: [ * products: [
* { productId: 'iphone_15' }, * { productId: 'iphone_15' },
* { productId: 'macbook_air' }, * { productId: 'macbook_air' },
* { productId: 'apple_watch' } * { productId: 'apple_watch' }
* ] * ],
* }, * body: 'Check out our featured products!'
* body: 'Check out our featured products!' * }
* }) * })
* ``` * ```
*/ */
export type ProductCarouselMessageOptions = { export type ProductCarouselMessageOptions = {
/** Catalog ID from WhatsApp Business */ /** JID of the business owner (catalog owner) - e.g., '5511999999999@s.whatsapp.net' */
catalogId: string businessOwnerJid: string
/** Products to display (2-10 cards required) */ /** Products to display (2-10 cards required) */
products: ProductCarouselCard[] products: ProductCarouselCard[]
/** Body text for the message */ /** Body text for the carousel message */
body?: string body?: string
} }
@@ -795,19 +795,18 @@ export type AnyRegularMessageContent = (
* ```typescript * ```typescript
* await sock.sendMessage(jid, { * await sock.sendMessage(jid, {
* productCarousel: { * productCarousel: {
* catalogId: '123456789', * businessOwnerJid: '5511999999999@s.whatsapp.net',
* products: [ * products: [
* { productId: 'produto_001' }, * { productId: 'produto_001' },
* { productId: 'produto_002' }, * { productId: 'produto_002' },
* { productId: 'produto_003' } * { productId: 'produto_003' }
* ] * ],
* }, * body: 'Confira nossos produtos em destaque!'
* body: 'Confira nossos produtos em destaque!' * }
* }) * })
* ``` * ```
*/ */
productCarousel: ProductCarouselMessageOptions productCarousel: ProductCarouselMessageOptions
body?: string
} }
| { | {
/** /**
+20 -14
View File
@@ -888,10 +888,13 @@ export const generateProductListMessage = (options: ProductListMessageOptions):
* Generates a product carousel message using products from WhatsApp Business catalog * Generates a product carousel message using products from WhatsApp Business catalog
* Uses viewOnceMessage wrapper for better iOS/Android compatibility * Uses viewOnceMessage wrapper for better iOS/Android compatibility
* *
* Each card in the carousel references a product from the business catalog using
* collectionMessage with bizJid (business owner) and id (product ID).
*
* @example * @example
* ```typescript * ```typescript
* const msg = generateProductCarouselMessage({ * const msg = generateProductCarouselMessage({
* catalogId: '123456789', * businessOwnerJid: '5511999999999@s.whatsapp.net',
* products: [ * products: [
* { productId: 'produto_001' }, * { productId: 'produto_001' },
* { productId: 'produto_002' }, * { productId: 'produto_002' },
@@ -905,10 +908,10 @@ export const generateProductListMessage = (options: ProductListMessageOptions):
export const generateProductCarouselMessage = ( export const generateProductCarouselMessage = (
options: ProductCarouselMessageOptions options: ProductCarouselMessageOptions
): WAMessageContent => { ): WAMessageContent => {
const { catalogId, products, body } = options const { businessOwnerJid, products, body } = options
if (!catalogId || typeof catalogId !== 'string' || catalogId.trim().length === 0) { if (!businessOwnerJid || typeof businessOwnerJid !== 'string' || businessOwnerJid.trim().length === 0) {
throw new Boom('catalogId is required and must be a non-empty string', { statusCode: 400 }) throw new Boom('businessOwnerJid is required and must be a non-empty string', { statusCode: 400 })
} }
if (!products || products.length < 2) { if (!products || products.length < 2) {
@@ -927,13 +930,16 @@ export const generateProductCarouselMessage = (
} }
} }
// Build cards array with product references // Normalize business owner JID
const cards = products.map((product, index) => ({ const normalizedBizJid = jidNormalizedUser(businessOwnerJid)
card_index: index,
type: 'product' as const, // Build cards array - each card is an IInteractiveMessage with collectionMessage
action: { // collectionMessage references a product from the business catalog
product_retailer_id: product.productId, const cards: proto.Message.IInteractiveMessage[] = products.map((product) => ({
catalog_id: catalogId collectionMessage: {
bizJid: normalizedBizJid,
id: product.productId,
messageVersion: 1
} }
})) }))
@@ -941,7 +947,7 @@ export const generateProductCarouselMessage = (
const interactiveMessage: proto.Message.IInteractiveMessage = { const interactiveMessage: proto.Message.IInteractiveMessage = {
body: { text: body || '' }, body: { text: body || '' },
carouselMessage: { carouselMessage: {
cards: cards as any, cards,
messageVersion: 1 messageVersion: 1
} }
} }
@@ -1100,9 +1106,9 @@ export const generateWAMessageContent = async (
else if (hasNonNullishProperty(message, 'productCarousel')) { else if (hasNonNullishProperty(message, 'productCarousel')) {
const productCarouselMsg = message as any const productCarouselMsg = message as any
const productCarouselOptions: ProductCarouselMessageOptions = { const productCarouselOptions: ProductCarouselMessageOptions = {
catalogId: productCarouselMsg.productCarousel.catalogId, businessOwnerJid: productCarouselMsg.productCarousel.businessOwnerJid,
products: productCarouselMsg.productCarousel.products, products: productCarouselMsg.productCarousel.products,
body: productCarouselMsg.body body: productCarouselMsg.productCarousel.body
} }
const generated = generateProductCarouselMessage(productCarouselOptions) const generated = generateProductCarouselMessage(productCarouselOptions)
m.viewOnceMessage = generated.viewOnceMessage m.viewOnceMessage = generated.viewOnceMessage