feat: send pix button

This commit is contained in:
Davidson Gomes 2024-10-29 07:36:24 -03:00
parent 9f39ec2110
commit fce3e55e91
3 changed files with 133 additions and 6 deletions

View File

@ -94,15 +94,21 @@ export class SendAudioDto extends Metadata {
audio: string;
}
export type TypeButton = 'reply' | 'copy' | 'url' | 'call';
export type TypeButton = 'reply' | 'copy' | 'url' | 'call' | 'pix';
export type KeyType = 'phone' | 'email' | 'cpf' | 'cnpj' | 'random';
export class Button {
type: TypeButton;
displayText: string;
displayText?: string;
id?: string;
url?: string;
copyCode?: string;
phoneNumber?: string;
currency?: string;
name?: string;
keyType?: KeyType;
key?: string;
}
export class SendButtonsDto extends Metadata {

View File

@ -33,6 +33,7 @@ import { HandleLabelDto, LabelDto } from '@api/dto/label.dto';
import {
Button,
ContactMessage,
KeyType,
MediaMessage,
Options,
SendAudioDto,
@ -1408,12 +1409,12 @@ export class BaileysStartupService extends ChannelStartupService {
});
const existingChat = await this.prismaRepository.chat.findFirst({
where: { instanceId: this.instanceId, remoteJid: message.key.remoteJid },
where: { instanceId: this.instanceId, remoteJid: message.remoteJid },
});
if (!!existingChat) {
const chatToInsert = {
remoteJid: message.key.remoteJid,
remoteJid: message.remoteJid,
instanceId: this.instanceId,
name: message.pushName || '',
unreadMessages: 0,
@ -2831,6 +2832,15 @@ export class BaileysStartupService extends ChannelStartupService {
);
}
private generateRandomId(length = 11) {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
}
private toJSONString(button: Button): string {
const toString = (obj: any) => JSON.stringify(obj);
@ -2844,6 +2854,49 @@ export class BaileysStartupService extends ChannelStartupService {
url: button.url,
merchant_url: button.url,
}),
pix: () =>
toString({
currency: button.currency,
total_amount: {
value: 0,
offset: 100
},
reference_id: this.generateRandomId(),
type: "physical-goods",
order: {
status: "pending",
subtotal: {
value: 0,
offset: 100
},
order_type: "ORDER",
items: [
{
name: "",
amount: {
value: 0,
offset: 100
},
quantity: 0,
sale_amount: {
value: 0,
offset: 100
}
}
]
},
payment_settings: [
{
type: "pix_static_code",
pix_static_code: {
merchant_name: button.name,
key: button.key,
key_type: this.mapKeyType.get(button.keyType)
}
}
],
share_payment_status: false
}),
};
return json[button.type]?.() || '';
@ -2854,9 +2907,73 @@ export class BaileysStartupService extends ChannelStartupService {
['copy', 'cta_copy'],
['url', 'cta_url'],
['call', 'cta_call'],
['pix', 'payment_info'],
]);
private readonly mapKeyType = new Map<KeyType, string>([
['phone', 'PHONE'],
['email', 'EMAIL'],
['cpf', 'CPF'],
['cnpj', 'CNPJ'],
['random', 'EVP'],
]);
public async buttonMessage(data: SendButtonsDto) {
if (data.buttons.length === 0) {
throw new BadRequestException('At least one button is required');
}
const hasReplyButtons = data.buttons.some(btn => btn.type === 'reply');
const hasPixButton = data.buttons.some(btn => btn.type === 'pix');
const hasOtherButtons = data.buttons.some(btn => btn.type !== 'reply' && btn.type !== 'pix');
if (hasReplyButtons) {
if (data.buttons.length > 3) {
throw new BadRequestException('Maximum of 3 reply buttons allowed');
}
if (hasOtherButtons) {
throw new BadRequestException('Reply buttons cannot be mixed with other button types');
}
}
if (hasPixButton) {
if (data.buttons.length > 1) {
throw new BadRequestException('Only one PIX button is allowed');
}
if (hasOtherButtons) {
throw new BadRequestException('PIX button cannot be mixed with other button types');
}
const message: proto.IMessage = {
viewOnceMessage: {
message: {
interactiveMessage: {
nativeFlowMessage: {
buttons: [{
name: this.mapType.get('pix'),
buttonParamsJson: this.toJSONString(data.buttons[0]),
}],
messageParamsJson: JSON.stringify({
from: 'api',
templateId: v4(),
}),
},
},
},
},
};
return await this.sendMessageWithTyping(data.number, message, {
delay: data?.delay,
presence: 'composing',
quoted: data?.quoted,
mentionsEveryOne: data?.mentionsEveryOne,
mentioned: data?.mentioned,
});
}
const generate = await (async () => {
if (data?.thumbnailUrl) {
return await this.prepareMediaMessage({

View File

@ -413,14 +413,18 @@ export const buttonsMessageSchema: JSONSchema7 = {
properties: {
type: {
type: 'string',
enum: ['reply', 'copy', 'url', 'call'],
enum: ['reply', 'copy', 'url', 'call', 'pix'],
},
displayText: { type: 'string' },
id: { type: 'string' },
url: { type: 'string' },
phoneNumber: { type: 'string' },
currency: { type: 'string' },
name: { type: 'string' },
keyType: { type: 'string', enum: ['phone', 'email', 'cpf', 'cnpj', 'random'] },
key: { type: 'string' },
},
required: ['type', 'displayText'],
required: ['type'],
...isNotEmpty('id', 'url', 'phoneNumber'),
},
},