From 9ddac98b25996b2611a9ee9f6b980a14826c62f1 Mon Sep 17 00:00:00 2001 From: Thiago Lugarini Date: Sat, 26 Sep 2026 10:49:05 -0300 Subject: [PATCH] fix(baileys): render the PIX button on Android/iOS, not only on WhatsApp Web Since 2.4.0 the PIX button (`payment_info`) shows on WhatsApp Web, but on phones the message is delivered and nothing is displayed - neither the card nor an error - on the sender's and on the recipient's device. Mobile clients render the payment card only when the message carries: - `messageParamsJson` `{"native_flow_name":"order_details","version":1}` and `messageVersion: 1`; - `messageContextInfo.messageSecret`; - a `` node whose native_flow is named `payment_info` (the generic `mixed` node is enough for Web only); - `` in 1:1 chats. This matches what evolution-go already sends. One difference on purpose: evolution-go wraps the message in `documentWithCaptionMessage`; with that wrapper the card shows on phones but disappears on Web, so it is sent unwrapped (as wa-js does from inside WhatsApp Web itself). The button title, previously ignored for PIX, becomes the card body. Refs #2467 --- .../helpers/interactiveMessage.helper.ts | 30 +++++++++++++++++++ .../whatsapp/whatsapp.baileys.service.ts | 25 +++++++++++----- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/helpers/interactiveMessage.helper.ts b/src/api/integrations/channel/whatsapp/helpers/interactiveMessage.helper.ts index eb132db27a..3f7022dc01 100644 --- a/src/api/integrations/channel/whatsapp/helpers/interactiveMessage.helper.ts +++ b/src/api/integrations/channel/whatsapp/helpers/interactiveMessage.helper.ts @@ -15,6 +15,36 @@ export function buildInteractiveBizNode(): BinaryNode { }; } +/** + * Stanza nodes for the PIX button (`payment_info`). + * + * The generic `mixed` node above is enough for WhatsApp Web, but Android/iOS + * only render the payment card when the native_flow is named `payment_info` + * and, in 1:1 chats, when the stanza also carries ``. + * Without them the message is delivered but nothing shows up on the phone. + */ +export function buildPixBizNodes(jid: string): BinaryNode[] { + const nodes: BinaryNode[] = [ + { + tag: 'biz', + attrs: {}, + content: [ + { + tag: 'interactive', + attrs: { type: 'native_flow', v: '1' }, + content: [{ tag: 'native_flow', attrs: { name: 'payment_info' } }], + }, + ], + }, + ]; + + if (!/@(g\.us|newsletter|broadcast)$/.test(jid)) { + nodes.push({ tag: 'bot', attrs: { biz_bot: '1' } }); + } + + return nodes; +} + /** * Biz node específico para `listMessage` legado. * Necessário para o WhatsApp Web/Desktop renderizar a lista — o moderno diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index 22839fd451..cd5b57fa0f 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -139,7 +139,7 @@ import { Label } from 'baileys/lib/Types/Label'; import { LabelAssociation } from 'baileys/lib/Types/LabelAssociation'; import { spawn } from 'child_process'; import { isArray, isBase64, isURL } from 'class-validator'; -import { createHash } from 'crypto'; +import { createHash, randomBytes } from 'crypto'; import EventEmitter2 from 'eventemitter2'; import ffmpeg from 'fluent-ffmpeg'; import FormData from 'form-data'; @@ -157,7 +157,12 @@ import { PassThrough, Readable } from 'stream'; import { v4 } from 'uuid'; import { BaileysMessageProcessor } from './baileysMessage.processor'; -import { buildInteractiveBizNode, buildListBizNode, toNativeFlowButton } from './helpers/interactiveMessage.helper'; +import { + buildInteractiveBizNode, + buildListBizNode, + buildPixBizNodes, + toNativeFlowButton, +} from './helpers/interactiveMessage.helper'; import { useVoiceCallsBaileys } from './voiceCalls/useVoiceCallsBaileys'; export interface ExtendedIMessageKey extends proto.IMessageKey { @@ -3671,8 +3676,13 @@ export class BaileysStartupService extends ChannelStartupService { throw new BadRequestException('PIX button cannot be mixed with other button types'); } + // Mobile clients need the `order_details` params, `messageVersion` and a + // `messageSecret` to render the payment card; WhatsApp Web renders it + // either way. Do NOT wrap it in `documentWithCaptionMessage`: mobile + // accepts the wrapper, but Web then shows nothing. const message: proto.IMessage = { interactiveMessage: { + ...(data?.title ? { body: { text: data.title } } : {}), nativeFlowMessage: { buttons: [ { @@ -3680,12 +3690,13 @@ export class BaileysStartupService extends ChannelStartupService { buttonParamsJson: this.toJSONString(data.buttons[0]), }, ], - messageParamsJson: JSON.stringify({ - from: 'api', - templateId: v4(), - }), + messageParamsJson: JSON.stringify({ native_flow_name: 'order_details', version: 1 }), + messageVersion: 1, }, }, + messageContextInfo: { + messageSecret: randomBytes(32), + }, }; return await this.sendMessageWithTyping( @@ -3699,7 +3710,7 @@ export class BaileysStartupService extends ChannelStartupService { mentioned: data?.mentioned, }, false, - [buildInteractiveBizNode()], + buildPixBizNodes(createJid(data.number)), ); }