Skip to content

Commit ed38aea

Browse files
committed
feat(messages): add sendProduct endpoint for WhatsApp Business catalog cards
1 parent 31c61d2 commit ed38aea

4 files changed

Lines changed: 73 additions & 0 deletions

File tree

src/api/controllers/sendMessage.controller.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
SendLocationDto,
99
SendMediaDto,
1010
SendPollDto,
11+
SendProductDto,
1112
SendPtvDto,
1213
SendReactionDto,
1314
SendStatusDto,
@@ -106,6 +107,13 @@ export class SendMessageController {
106107
return await this.waMonitor.waInstances[instanceName].pollMessage(data);
107108
}
108109

110+
public async sendProduct({ instanceName }: InstanceDto, data: SendProductDto) {
111+
if (!isURL(data?.productImage) && !isBase64(data?.productImage)) {
112+
throw new BadRequestException('productImage must be a URL or base64 string');
113+
}
114+
return await this.waMonitor.waInstances[instanceName].productMessage(data);
115+
}
116+
109117
public async sendStatus({ instanceName }: InstanceDto, data: SendStatusDto, file?: any) {
110118
return await this.waMonitor.waInstances[instanceName].statusMessage(data, file);
111119
}

src/api/dto/sendMessage.dto.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ export class SendReactionDto {
174174
reaction: string;
175175
}
176176

177+
<<<<<<< HEAD
177178
export class CarouselCard {
178179
title?: string;
179180
body: string;
@@ -185,4 +186,29 @@ export class CarouselCard {
185186
export class SendCarouselDto extends Metadata {
186187
body: string;
187188
cards: CarouselCard[];
189+
=======
190+
export class SendProductDto extends Metadata {
191+
/** WhatsApp internal product id (from /business/getCatalog `id`) */
192+
productId: string;
193+
/** Business owner JID — `<phone>@s.whatsapp.net` of the catalog owner */
194+
businessOwnerJid: string;
195+
/** Product image — URL or base64 */
196+
productImage: string;
197+
/** Merchant-side retailer id (e.g. `BD3`). Optional. */
198+
retailerId?: string;
199+
/** Product title shown to recipients as a fallback. */
200+
title?: string;
201+
/** Product description shown as a fallback. */
202+
description?: string;
203+
/** ISO 4217 currency code (e.g. `ILS`, `USD`). Defaults to `USD`. */
204+
currencyCode?: string;
205+
/** Price × 1000 (e.g. 5500 ILS → `5500000`). */
206+
priceAmount1000?: number;
207+
/** Product landing URL. Optional. */
208+
url?: string;
209+
/** How many images the product has in the catalog. Defaults to 1. */
210+
productImageCount?: number;
211+
/** Optional caption sent alongside the product card. */
212+
caption?: string;
213+
>>>>>>> 97a314b9 (feat(messages): add sendProduct endpoint for WhatsApp Business catalog cards)
188214
}

src/api/routes/sendMessage.router.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
SendLocationDto,
99
SendMediaDto,
1010
SendPollDto,
11+
SendProductDto,
1112
SendPtvDto,
1213
SendReactionDto,
1314
SendStatusDto,
@@ -25,6 +26,7 @@ import {
2526
locationMessageSchema,
2627
mediaMessageSchema,
2728
pollMessageSchema,
29+
productMessageSchema,
2830
ptvMessageSchema,
2931
reactionMessageSchema,
3032
statusMessageSchema,
@@ -164,6 +166,16 @@ export class MessageRouter extends RouterBroker {
164166

165167
return res.status(HttpStatus.CREATED).json(response);
166168
})
169+
.post(this.routerPath('sendProduct'), ...guards, async (req, res) => {
170+
const response = await this.dataValidate<SendProductDto>({
171+
request: req,
172+
schema: productMessageSchema,
173+
ClassRef: SendProductDto,
174+
execute: (instance, data) => sendMessageController.sendProduct(instance, data),
175+
});
176+
177+
return res.status(HttpStatus.CREATED).json(response);
178+
})
167179
.post(this.routerPath('sendList'), ...guards, async (req, res) => {
168180
const response = await this.dataValidate<SendListDto>({
169181
request: req,

src/validate/message.schema.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,11 +457,16 @@ export const buttonsMessageSchema: JSONSchema7 = {
457457
required: ['number'],
458458
};
459459

460+
<<<<<<< HEAD
460461
export const carouselMessageSchema: JSONSchema7 = {
462+
=======
463+
export const productMessageSchema: JSONSchema7 = {
464+
>>>>>>> 97a314b9 (feat(messages): add sendProduct endpoint for WhatsApp Business catalog cards)
461465
$id: v4(),
462466
type: 'object',
463467
properties: {
464468
number: { ...numberDefinition },
469+
<<<<<<< HEAD
465470
body: { type: 'string', minLength: 1 },
466471
cards: {
467472
type: 'array',
@@ -536,4 +541,26 @@ export const decryptPollVoteSchema: JSONSchema7 = {
536541
remoteJid: { type: 'string' },
537542
},
538543
required: ['message', 'remoteJid'],
544+
=======
545+
productId: { type: 'string', minLength: 1 },
546+
businessOwnerJid: {
547+
type: 'string',
548+
pattern: '^[0-9]+@s[.]whatsapp[.]net$',
549+
description: '"businessOwnerJid" must look like "<phone>@s.whatsapp.net"',
550+
},
551+
productImage: { type: 'string', minLength: 1 },
552+
retailerId: { type: 'string' },
553+
title: { type: 'string' },
554+
description: { type: 'string' },
555+
currencyCode: { type: 'string', minLength: 3, maxLength: 3 },
556+
priceAmount1000: { type: 'integer', minimum: 0 },
557+
url: { type: 'string' },
558+
productImageCount: { type: 'integer', minimum: 1 },
559+
caption: { type: 'string' },
560+
delay: { type: 'integer', description: 'Enter a value in milliseconds' },
561+
quoted: { ...quotedOptionsSchema },
562+
},
563+
required: ['number', 'productId', 'businessOwnerJid', 'productImage'],
564+
...isNotEmpty('number', 'productId', 'businessOwnerJid', 'productImage'),
565+
>>>>>>> 97a314b9 (feat(messages): add sendProduct endpoint for WhatsApp Business catalog cards)
539566
};

0 commit comments

Comments
 (0)