From 31c61d215d4f2e84de2b03cc3d4bd2cd3ee5145c Mon Sep 17 00:00:00 2001 From: yarninz Date: Mon, 15 Jun 2026 16:16:24 +0300 Subject: [PATCH 1/3] fix(baileys): pass product content directly to Baileys sendMessage --- .../whatsapp/whatsapp.baileys.service.ts | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 22839fd45..1d7255c4c 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -48,6 +48,7 @@ import { SendLocationDto, SendMediaDto, SendPollDto, + SendProductDto, SendPtvDto, SendReactionDto, SendStatusDto, @@ -2506,6 +2507,13 @@ export class BaileysStartupService extends ChannelStartupService { ); } + if (message['product']) { + return await this.client.sendMessage( + sender, + message as unknown as AnyMessageContent, + option as unknown as MiscMessageGenerationOptions, + ); + } if (!message['audio'] && !message['poll'] && !message['sticker'] && sender != 'status@broadcast') { return await this.client.sendMessage( sender, @@ -2959,6 +2967,46 @@ export class BaileysStartupService extends ChannelStartupService { ); } + public async productMessage(data: SendProductDto, isIntegration = false) { + if (!data.productId || data.productId.trim().length === 0) { + throw new BadRequestException('productId is required'); + } + if (!data.businessOwnerJid || data.businessOwnerJid.trim().length === 0) { + throw new BadRequestException('businessOwnerJid is required'); + } + if (!data.productImage || data.productImage.trim().length === 0) { + throw new BadRequestException('productImage is required'); + } + + const productImage = /^https?:\/\//i.test(data.productImage) + ? { url: data.productImage } + : Buffer.from(data.productImage, 'base64'); + + return await this.sendMessageWithTyping( + data.number, + { + product: { + productImage, + productId: data.productId, + title: data.title ?? '', + description: data.description ?? '', + currencyCode: data.currencyCode ?? 'USD', + priceAmount1000: data.priceAmount1000 != null ? String(data.priceAmount1000) : undefined, + retailerId: data.retailerId ?? '', + url: data.url ?? '', + productImageCount: data.productImageCount ?? 1, + }, + businessOwnerJid: data.businessOwnerJid, + caption: data.caption ?? '', + }, + { + delay: data?.delay, + presence: 'composing', + quoted: data?.quoted, + }, + isIntegration, + ); + } public async pollMessage(data: SendPollDto) { return await this.sendMessageWithTyping( data.number, From ed38aeaa13d26853249c052809468a86d5ccd790 Mon Sep 17 00:00:00 2001 From: yarninz Date: Mon, 15 Jun 2026 16:16:38 +0300 Subject: [PATCH 2/3] feat(messages): add sendProduct endpoint for WhatsApp Business catalog cards --- src/api/controllers/sendMessage.controller.ts | 8 ++++++ src/api/dto/sendMessage.dto.ts | 26 ++++++++++++++++++ src/api/routes/sendMessage.router.ts | 12 +++++++++ src/validate/message.schema.ts | 27 +++++++++++++++++++ 4 files changed, 73 insertions(+) diff --git a/src/api/controllers/sendMessage.controller.ts b/src/api/controllers/sendMessage.controller.ts index 8414c67db..c25cc0792 100644 --- a/src/api/controllers/sendMessage.controller.ts +++ b/src/api/controllers/sendMessage.controller.ts @@ -8,6 +8,7 @@ import { SendLocationDto, SendMediaDto, SendPollDto, + SendProductDto, SendPtvDto, SendReactionDto, SendStatusDto, @@ -106,6 +107,13 @@ export class SendMessageController { return await this.waMonitor.waInstances[instanceName].pollMessage(data); } + public async sendProduct({ instanceName }: InstanceDto, data: SendProductDto) { + if (!isURL(data?.productImage) && !isBase64(data?.productImage)) { + throw new BadRequestException('productImage must be a URL or base64 string'); + } + return await this.waMonitor.waInstances[instanceName].productMessage(data); + } + public async sendStatus({ instanceName }: InstanceDto, data: SendStatusDto, file?: any) { return await this.waMonitor.waInstances[instanceName].statusMessage(data, file); } diff --git a/src/api/dto/sendMessage.dto.ts b/src/api/dto/sendMessage.dto.ts index b3d87e5e0..7fc482b59 100644 --- a/src/api/dto/sendMessage.dto.ts +++ b/src/api/dto/sendMessage.dto.ts @@ -174,6 +174,7 @@ export class SendReactionDto { reaction: string; } +<<<<<<< HEAD export class CarouselCard { title?: string; body: string; @@ -185,4 +186,29 @@ export class CarouselCard { export class SendCarouselDto extends Metadata { body: string; cards: CarouselCard[]; +======= +export class SendProductDto extends Metadata { + /** WhatsApp internal product id (from /business/getCatalog `id`) */ + productId: string; + /** Business owner JID — `@s.whatsapp.net` of the catalog owner */ + businessOwnerJid: string; + /** Product image — URL or base64 */ + productImage: string; + /** Merchant-side retailer id (e.g. `BD3`). Optional. */ + retailerId?: string; + /** Product title shown to recipients as a fallback. */ + title?: string; + /** Product description shown as a fallback. */ + description?: string; + /** ISO 4217 currency code (e.g. `ILS`, `USD`). Defaults to `USD`. */ + currencyCode?: string; + /** Price × 1000 (e.g. 5500 ILS → `5500000`). */ + priceAmount1000?: number; + /** Product landing URL. Optional. */ + url?: string; + /** How many images the product has in the catalog. Defaults to 1. */ + productImageCount?: number; + /** Optional caption sent alongside the product card. */ + caption?: string; +>>>>>>> 97a314b9 (feat(messages): add sendProduct endpoint for WhatsApp Business catalog cards) } diff --git a/src/api/routes/sendMessage.router.ts b/src/api/routes/sendMessage.router.ts index 51876483d..19486f33e 100644 --- a/src/api/routes/sendMessage.router.ts +++ b/src/api/routes/sendMessage.router.ts @@ -8,6 +8,7 @@ import { SendLocationDto, SendMediaDto, SendPollDto, + SendProductDto, SendPtvDto, SendReactionDto, SendStatusDto, @@ -25,6 +26,7 @@ import { locationMessageSchema, mediaMessageSchema, pollMessageSchema, + productMessageSchema, ptvMessageSchema, reactionMessageSchema, statusMessageSchema, @@ -164,6 +166,16 @@ export class MessageRouter extends RouterBroker { return res.status(HttpStatus.CREATED).json(response); }) + .post(this.routerPath('sendProduct'), ...guards, async (req, res) => { + const response = await this.dataValidate({ + request: req, + schema: productMessageSchema, + ClassRef: SendProductDto, + execute: (instance, data) => sendMessageController.sendProduct(instance, data), + }); + + return res.status(HttpStatus.CREATED).json(response); + }) .post(this.routerPath('sendList'), ...guards, async (req, res) => { const response = await this.dataValidate({ request: req, diff --git a/src/validate/message.schema.ts b/src/validate/message.schema.ts index db76fe1c8..396f41f39 100644 --- a/src/validate/message.schema.ts +++ b/src/validate/message.schema.ts @@ -457,11 +457,16 @@ export const buttonsMessageSchema: JSONSchema7 = { required: ['number'], }; +<<<<<<< HEAD export const carouselMessageSchema: JSONSchema7 = { +======= +export const productMessageSchema: JSONSchema7 = { +>>>>>>> 97a314b9 (feat(messages): add sendProduct endpoint for WhatsApp Business catalog cards) $id: v4(), type: 'object', properties: { number: { ...numberDefinition }, +<<<<<<< HEAD body: { type: 'string', minLength: 1 }, cards: { type: 'array', @@ -536,4 +541,26 @@ export const decryptPollVoteSchema: JSONSchema7 = { remoteJid: { type: 'string' }, }, required: ['message', 'remoteJid'], +======= + productId: { type: 'string', minLength: 1 }, + businessOwnerJid: { + type: 'string', + pattern: '^[0-9]+@s[.]whatsapp[.]net$', + description: '"businessOwnerJid" must look like "@s.whatsapp.net"', + }, + productImage: { type: 'string', minLength: 1 }, + retailerId: { type: 'string' }, + title: { type: 'string' }, + description: { type: 'string' }, + currencyCode: { type: 'string', minLength: 3, maxLength: 3 }, + priceAmount1000: { type: 'integer', minimum: 0 }, + url: { type: 'string' }, + productImageCount: { type: 'integer', minimum: 1 }, + caption: { type: 'string' }, + delay: { type: 'integer', description: 'Enter a value in milliseconds' }, + quoted: { ...quotedOptionsSchema }, + }, + required: ['number', 'productId', 'businessOwnerJid', 'productImage'], + ...isNotEmpty('number', 'productId', 'businessOwnerJid', 'productImage'), +>>>>>>> 97a314b9 (feat(messages): add sendProduct endpoint for WhatsApp Business catalog cards) }; From cd45c5c120f8032becba4c0af54b41eecca5c035 Mon Sep 17 00:00:00 2001 From: yarninz Date: Fri, 26 Jun 2026 12:43:05 +0300 Subject: [PATCH 3/3] fix(messages): resolve sendMessage conflict merge --- src/api/dto/sendMessage.dto.ts | 5 ++- src/validate/message.schema.ts | 63 ++++++++++++++++++++-------------- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/api/dto/sendMessage.dto.ts b/src/api/dto/sendMessage.dto.ts index 7fc482b59..608888258 100644 --- a/src/api/dto/sendMessage.dto.ts +++ b/src/api/dto/sendMessage.dto.ts @@ -174,7 +174,6 @@ export class SendReactionDto { reaction: string; } -<<<<<<< HEAD export class CarouselCard { title?: string; body: string; @@ -186,7 +185,8 @@ export class CarouselCard { export class SendCarouselDto extends Metadata { body: string; cards: CarouselCard[]; -======= +} + export class SendProductDto extends Metadata { /** WhatsApp internal product id (from /business/getCatalog `id`) */ productId: string; @@ -210,5 +210,4 @@ export class SendProductDto extends Metadata { productImageCount?: number; /** Optional caption sent alongside the product card. */ caption?: string; ->>>>>>> 97a314b9 (feat(messages): add sendProduct endpoint for WhatsApp Business catalog cards) } diff --git a/src/validate/message.schema.ts b/src/validate/message.schema.ts index 396f41f39..df916b8c3 100644 --- a/src/validate/message.schema.ts +++ b/src/validate/message.schema.ts @@ -457,16 +457,11 @@ export const buttonsMessageSchema: JSONSchema7 = { required: ['number'], }; -<<<<<<< HEAD export const carouselMessageSchema: JSONSchema7 = { -======= -export const productMessageSchema: JSONSchema7 = { ->>>>>>> 97a314b9 (feat(messages): add sendProduct endpoint for WhatsApp Business catalog cards) $id: v4(), type: 'object', properties: { number: { ...numberDefinition }, -<<<<<<< HEAD body: { type: 'string', minLength: 1 }, cards: { type: 'array', @@ -486,7 +481,7 @@ export const productMessageSchema: JSONSchema7 = { items: { type: 'object', properties: { - type: { type: 'string', enum: ['reply', 'copy', 'url', 'call'] }, + type: { type: 'string', enum: ['reply', 'copy', 'url', 'call', 'pix'] }, displayText: { type: 'string' }, id: { type: 'string' }, url: { type: 'string' }, @@ -506,7 +501,7 @@ export const productMessageSchema: JSONSchema7 = { description: 'Enter a value in milliseconds', }, quoted: { ...quotedOptionsSchema }, - everyOne: { type: 'boolean', enum: [true, false] }, + mentionsEveryOne: { type: 'boolean', enum: [true, false] }, mentioned: { type: 'array', minItems: 1, @@ -521,27 +516,11 @@ export const productMessageSchema: JSONSchema7 = { required: ['number', 'body', 'cards'], }; -export const decryptPollVoteSchema: JSONSchema7 = { +export const productMessageSchema: JSONSchema7 = { $id: v4(), type: 'object', properties: { - message: { - type: 'object', - properties: { - key: { - type: 'object', - properties: { - id: { type: 'string' }, - }, - required: ['id'], - }, - }, - required: ['key'], - }, - remoteJid: { type: 'string' }, - }, - required: ['message', 'remoteJid'], -======= + number: { ...numberDefinition }, productId: { type: 'string', minLength: 1 }, businessOwnerJid: { type: 'string', @@ -559,8 +538,40 @@ export const decryptPollVoteSchema: JSONSchema7 = { caption: { type: 'string' }, delay: { type: 'integer', description: 'Enter a value in milliseconds' }, quoted: { ...quotedOptionsSchema }, + mentionsEveryOne: { type: 'boolean', enum: [true, false] }, + mentioned: { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { + type: 'string', + pattern: '^\\d+', + description: '"mentioned" must be an array of numeric strings', + }, + }, }, required: ['number', 'productId', 'businessOwnerJid', 'productImage'], ...isNotEmpty('number', 'productId', 'businessOwnerJid', 'productImage'), ->>>>>>> 97a314b9 (feat(messages): add sendProduct endpoint for WhatsApp Business catalog cards) +}; + +export const decryptPollVoteSchema: JSONSchema7 = { + $id: v4(), + type: 'object', + properties: { + message: { + type: 'object', + properties: { + key: { + type: 'object', + properties: { + id: { type: 'string' }, + }, + required: ['id'], + }, + }, + required: ['key'], + }, + remoteJid: { type: 'string' }, + }, + required: ['message', 'remoteJid'], };