Skip to content

Commit d4e8cb9

Browse files
committed
feat: enhance button functionality with PIX support and additional properties
- Added 'pix' type to TypeButton and updated Button class to include optional fields: currency, name, keyType, and key. - Introduced KeyType enum for better key management. - Updated buttonMessage method to enforce rules for mixing button types, specifically for PIX buttons. - Enhanced buttonsMessageSchema to validate new button properties and types.
1 parent a306f15 commit d4e8cb9

File tree

3 files changed

+126
-71
lines changed

3 files changed

+126
-71
lines changed

src/api/dto/sendMessage.dto.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,20 @@ export class SendAudioDto extends Metadata {
9494
audio: string;
9595
}
9696

97-
export type TypeButton = 'reply' | 'copy' | 'url' | 'call';
97+
export type TypeButton = 'reply' | 'copy' | 'url' | 'call' | 'pix';
98+
export type KeyType = 'phone' | 'email' | 'cpf' | 'cnpj' | 'random';
9899

99100
export class Button {
100101
type: TypeButton;
101-
displayText: string;
102+
displayText?: string;
102103
id?: string;
103104
url?: string;
104105
copyCode?: string;
105106
phoneNumber?: string;
107+
currency?: string;
108+
name?: string;
109+
keyType?: KeyType;
110+
key?: string;
106111
}
107112

108113
export class SendButtonsDto extends Metadata {

src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

Lines changed: 113 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { HandleLabelDto, LabelDto } from '@api/dto/label.dto';
3333
import {
3434
Button,
3535
ContactMessage,
36+
KeyType,
3637
MediaMessage,
3738
Options,
3839
SendAudioDto,
@@ -272,7 +273,7 @@ export class BaileysStartupService extends ChannelStartupService {
272273
qrcodeTerminal.generate(qr, { small: true }, (qrcode) =>
273274
this.logger.log(
274275
`\n{ instance: ${this.instance.name} pairingCode: ${this.instance.qrcode.pairingCode}, qrcodeCount: ${this.instance.qrcode.count} }\n` +
275-
qrcode,
276+
qrcode,
276277
),
277278
);
278279

@@ -1988,9 +1989,7 @@ export class BaileysStartupService extends ChannelStartupService {
19881989

19891990
const prepareMedia = await prepareWAMessageMedia(
19901991
{
1991-
[type]: isURL(mediaMessage.media)
1992-
? { url: mediaMessage.media }
1993-
: Buffer.from(mediaMessage.media, 'base64'),
1992+
[type]: isURL(mediaMessage.media) ? { url: mediaMessage.media } : Buffer.from(mediaMessage.media, 'base64'),
19941993
} as any,
19951994
{ upload: this.client.waUploadToServer },
19961995
);
@@ -2154,7 +2153,7 @@ export class BaileysStartupService extends ChannelStartupService {
21542153
);
21552154
}
21562155

2157-
public async ptvMessage(data: SendPtvDto, file?: any, isIntegration = false) {
2156+
public async ptvMessage(data: SendPtvDto, file?: any) {
21582157
const mediaData: SendMediaDto = {
21592158
number: data.number,
21602159
media: data.video,
@@ -2230,6 +2229,15 @@ export class BaileysStartupService extends ChannelStartupService {
22302229
);
22312230
}
22322231

2232+
private generateRandomId(length = 11) {
2233+
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
2234+
let result = '';
2235+
for (let i = 0; i < length; i++) {
2236+
result += characters.charAt(Math.floor(Math.random() * characters.length));
2237+
}
2238+
return result;
2239+
}
2240+
22332241
private toJSONString(button: Button): string {
22342242
const toString = (obj: any) => JSON.stringify(obj);
22352243

@@ -2243,6 +2251,49 @@ export class BaileysStartupService extends ChannelStartupService {
22432251
url: button.url,
22442252
merchant_url: button.url,
22452253
}),
2254+
pix: () =>
2255+
toString({
2256+
currency: button.currency,
2257+
total_amount: {
2258+
value: 0,
2259+
offset: 100,
2260+
},
2261+
reference_id: this.generateRandomId(),
2262+
type: 'physical-goods',
2263+
order: {
2264+
status: 'pending',
2265+
subtotal: {
2266+
value: 0,
2267+
offset: 100,
2268+
},
2269+
order_type: 'ORDER',
2270+
items: [
2271+
{
2272+
name: '',
2273+
amount: {
2274+
value: 0,
2275+
offset: 100,
2276+
},
2277+
quantity: 0,
2278+
sale_amount: {
2279+
value: 0,
2280+
offset: 100,
2281+
},
2282+
},
2283+
],
2284+
},
2285+
payment_settings: [
2286+
{
2287+
type: 'pix_static_code',
2288+
pix_static_code: {
2289+
merchant_name: button.name,
2290+
key: button.key,
2291+
key_type: this.mapKeyType.get(button.keyType),
2292+
},
2293+
},
2294+
],
2295+
share_payment_status: false,
2296+
}),
22462297
};
22472298

22482299
return json[button.type]?.() || '';
@@ -2253,74 +2304,69 @@ export class BaileysStartupService extends ChannelStartupService {
22532304
['copy', 'cta_copy'],
22542305
['url', 'cta_url'],
22552306
['call', 'cta_call'],
2307+
['pix', 'payment_info'],
22562308
]);
22572309

2258-
public async buttonMessage(data: SendButtonsDto) {
2259-
const generate = await (async () => {
2260-
if (data?.thumbnailUrl) {
2261-
return await this.prepareMediaMessage({
2262-
mediatype: 'image',
2263-
media: data.thumbnailUrl,
2264-
});
2265-
}
2266-
})();
2267-
2268-
const buttons = data.buttons.map((value) => {
2269-
return {
2270-
name: this.mapType.get(value.type),
2271-
buttonParamsJson: this.toJSONString(value),
2272-
};
2273-
});
2310+
private readonly mapKeyType = new Map<KeyType, string>([
2311+
['phone', 'PHONE'],
2312+
['email', 'EMAIL'],
2313+
['cpf', 'CPF'],
2314+
['cnpj', 'CNPJ'],
2315+
['random', 'EVP'],
2316+
]);
22742317

2275-
const message: proto.IMessage = {
2276-
viewOnceMessage: {
2277-
message: {
2278-
messageContextInfo: {
2279-
deviceListMetadata: {},
2280-
deviceListMetadataVersion: 2,
2281-
},
2282-
interactiveMessage: {
2283-
body: {
2284-
text: (() => {
2285-
let t = '*' + data.title + '*';
2286-
if (data?.description) {
2287-
t += '\n\n';
2288-
t += data.description;
2289-
t += '\n';
2290-
}
2291-
return t;
2292-
})(),
2293-
},
2294-
footer: {
2295-
text: data?.footer,
2296-
},
2297-
header: (() => {
2298-
if (generate?.message?.imageMessage) {
2299-
return {
2300-
hasMediaAttachment: !!generate.message.imageMessage,
2301-
imageMessage: generate.message.imageMessage,
2302-
};
2303-
}
2304-
})(),
2305-
nativeFlowMessage: {
2306-
buttons: buttons,
2307-
messageParamsJson: JSON.stringify({
2308-
from: 'api',
2309-
templateId: v4(),
2310-
}),
2318+
public async buttonMessage(data: SendButtonsDto) {
2319+
if (data.buttons.length === 0) {
2320+
throw new BadRequestException('At least one button is required');
2321+
}
2322+
const hasReplyButtons = data.buttons.some((btn) => btn.type === 'reply');
2323+
2324+
const hasPixButton = data.buttons.some((btn) => btn.type === 'pix');
2325+
2326+
const hasOtherButtons = data.buttons.some((btn) => btn.type !== 'reply' && btn.type !== 'pix');
2327+
if (hasReplyButtons) {
2328+
if (data.buttons.length > 3) {
2329+
throw new BadRequestException('Maximum of 3 reply buttons allowed');
2330+
}
2331+
if (hasOtherButtons) {
2332+
throw new BadRequestException('Reply buttons cannot be mixed with other button types');
2333+
}
2334+
}
2335+
if (hasPixButton) {
2336+
if (data.buttons.length > 1) {
2337+
throw new BadRequestException('Only one PIX button is allowed');
2338+
}
2339+
if (hasOtherButtons) {
2340+
throw new BadRequestException('PIX button cannot be mixed with other button types');
2341+
}
2342+
const message: proto.IMessage = {
2343+
viewOnceMessage: {
2344+
message: {
2345+
interactiveMessage: {
2346+
nativeFlowMessage: {
2347+
buttons: [
2348+
{
2349+
name: this.mapType.get('pix'),
2350+
buttonParamsJson: this.toJSONString(data.buttons[0]),
2351+
},
2352+
],
2353+
messageParamsJson: JSON.stringify({
2354+
from: 'api',
2355+
templateId: v4(),
2356+
}),
2357+
},
23112358
},
23122359
},
23132360
},
2314-
},
2315-
};
2316-
2317-
return await this.sendMessageWithTyping(data.number, message, {
2318-
delay: data?.delay,
2319-
presence: 'composing',
2320-
quoted: data?.quoted,
2321-
mentionsEveryOne: data?.mentionsEveryOne,
2322-
mentioned: data?.mentioned,
2323-
});
2361+
};
2362+
return await this.sendMessageWithTyping(data.number, message, {
2363+
delay: data?.delay,
2364+
presence: 'composing',
2365+
quoted: data?.quoted,
2366+
mentionsEveryOne: data?.mentionsEveryOne,
2367+
mentioned: data?.mentioned,
2368+
});
2369+
}
23242370
}
23252371

23262372
public async listMessage(data: SendListDto) {

src/validate/message.schema.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,14 +402,18 @@ export const buttonsMessageSchema: JSONSchema7 = {
402402
properties: {
403403
type: {
404404
type: 'string',
405-
enum: ['reply', 'copy', 'url', 'call'],
405+
enum: ['reply', 'copy', 'url', 'call', 'pix'],
406406
},
407407
displayText: { type: 'string' },
408408
id: { type: 'string' },
409409
url: { type: 'string' },
410410
phoneNumber: { type: 'string' },
411+
currency: { type: 'string' },
412+
name: { type: 'string' },
413+
keyType: { type: 'string', enum: ['phone', 'email', 'cpf', 'cnpj', 'random'] },
414+
key: { type: 'string' },
411415
},
412-
required: ['type', 'displayText'],
416+
required: ['type'],
413417
...isNotEmpty('id', 'url', 'phoneNumber'),
414418
},
415419
},

0 commit comments

Comments
 (0)