@wondai/n8n-nodes-nucleo 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nodes/Nucleo/Nucleo.node.js +111 -32
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@ const n8n_workflow_1 = require("n8n-workflow");
|
|
|
15
15
|
* signature = HMAC_SHA256(signingSecret, canônica) em hex.
|
|
16
16
|
*/
|
|
17
17
|
const SIGNATURE_VERSION = "v1";
|
|
18
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
18
19
|
const HEADERS = {
|
|
19
20
|
key: "x-wondai-key",
|
|
20
21
|
timestamp: "x-wondai-timestamp",
|
|
@@ -338,6 +339,30 @@ class Nucleo {
|
|
|
338
339
|
description: 'Opcional. Para entrega, snapshot do endereço escolhido: {"logradouro","numero","complemento","bairro","cidade","estado","cep","referencia"}. Logradouro e cidade são obrigatórios quando enviado.',
|
|
339
340
|
displayOptions: { show: { resource: ["pedido"], operation: ["criar"] } },
|
|
340
341
|
},
|
|
342
|
+
{
|
|
343
|
+
displayName: "Forma de Pagamento",
|
|
344
|
+
name: "formaPagamento",
|
|
345
|
+
type: "options",
|
|
346
|
+
options: [
|
|
347
|
+
{ name: "(não informado)", value: "" },
|
|
348
|
+
{ name: "PIX", value: "pix" },
|
|
349
|
+
{ name: "Cartão", value: "cartao" },
|
|
350
|
+
{ name: "Dinheiro", value: "dinheiro" },
|
|
351
|
+
],
|
|
352
|
+
default: "",
|
|
353
|
+
description: "Opcional. Quando informado, registra um pagamento pendente no pedido. Para dinheiro na entrega, pergunte ao cliente se precisa de troco e preencha 'Troco Para'.",
|
|
354
|
+
displayOptions: { show: { resource: ["pedido"], operation: ["criar"] } },
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
displayName: "Troco Para",
|
|
358
|
+
name: "trocoPara",
|
|
359
|
+
type: "number",
|
|
360
|
+
default: 0,
|
|
361
|
+
description: "SÓ dinheiro na entrega: valor da NOTA que o cliente vai entregar (ex.: 50). Tem que ser ≥ total do pedido. 0/vazio = não precisa de troco. O troco a separar = troco_para − total.",
|
|
362
|
+
displayOptions: {
|
|
363
|
+
show: { resource: ["pedido"], operation: ["criar"], formaPagamento: ["dinheiro"] },
|
|
364
|
+
},
|
|
365
|
+
},
|
|
341
366
|
{
|
|
342
367
|
displayName: "Loja (unit_id)",
|
|
343
368
|
name: "unitId",
|
|
@@ -380,6 +405,27 @@ class Nucleo {
|
|
|
380
405
|
description: "ISO 8601. Envie vazio para não alterar.",
|
|
381
406
|
displayOptions: { show: { resource: ["pedido"], operation: ["alterar"] } },
|
|
382
407
|
},
|
|
408
|
+
{
|
|
409
|
+
displayName: "Tipo de Entrega (alterar)",
|
|
410
|
+
name: "tipoEntregaNova",
|
|
411
|
+
type: "options",
|
|
412
|
+
options: [
|
|
413
|
+
{ name: "(não alterar)", value: "" },
|
|
414
|
+
{ name: "Retirada", value: "retirada" },
|
|
415
|
+
{ name: "Entrega", value: "entrega" },
|
|
416
|
+
],
|
|
417
|
+
default: "",
|
|
418
|
+
description: "Troca a modalidade do pedido. Ao mudar para entrega, preencha também 'Endereço de Entrega' se já tiver o endereço.",
|
|
419
|
+
displayOptions: { show: { resource: ["pedido"], operation: ["alterar"] } },
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
displayName: "Endereço de Entrega (JSON)",
|
|
423
|
+
name: "enderecoEntregaNovo",
|
|
424
|
+
type: "json",
|
|
425
|
+
default: "{}",
|
|
426
|
+
description: 'Opcional. Snapshot do endereço quando a modalidade final for entrega: {"logradouro","numero","complemento","bairro","cidade","estado","cep","referencia"}. Logradouro e cidade obrigatórios quando enviado. Endereço com modalidade final retirada → erro.',
|
|
427
|
+
displayOptions: { show: { resource: ["pedido"], operation: ["alterar"] } },
|
|
428
|
+
},
|
|
383
429
|
{
|
|
384
430
|
displayName: "Adicionar Itens (JSON)",
|
|
385
431
|
name: "adicionarItens",
|
|
@@ -527,6 +573,8 @@ class Nucleo {
|
|
|
527
573
|
const observacoes = this.getNodeParameter("observacoes", i, "").trim();
|
|
528
574
|
const agendadoPara = this.getNodeParameter("agendadoPara", i, "").trim();
|
|
529
575
|
const enderecoEntrega = asObject(this.getNodeParameter("enderecoEntrega", i, "{}"));
|
|
576
|
+
const formaPagamento = this.getNodeParameter("formaPagamento", i, "").trim();
|
|
577
|
+
const trocoPara = Number(this.getNodeParameter("trocoPara", i, 0));
|
|
530
578
|
const unitId = this.getNodeParameter("unitId", i, "").trim();
|
|
531
579
|
idempotencyKey =
|
|
532
580
|
this.getNodeParameter("idempotencyKey", i, "").trim() || (0, node_crypto_1.randomUUID)();
|
|
@@ -542,6 +590,12 @@ class Nucleo {
|
|
|
542
590
|
if (enderecoEntrega && Object.keys(enderecoEntrega).length) {
|
|
543
591
|
b.endereco_entrega = enderecoEntrega;
|
|
544
592
|
}
|
|
593
|
+
if (formaPagamento)
|
|
594
|
+
b.forma_pagamento = formaPagamento;
|
|
595
|
+
// Troco só faz sentido com dinheiro; 0/negativo = "não precisa de troco" → não envia.
|
|
596
|
+
if (formaPagamento === "dinheiro" && Number.isFinite(trocoPara) && trocoPara > 0) {
|
|
597
|
+
b.troco_para = trocoPara;
|
|
598
|
+
}
|
|
545
599
|
if (unitId)
|
|
546
600
|
b.unit_id = unitId;
|
|
547
601
|
method = "POST";
|
|
@@ -552,6 +606,8 @@ class Nucleo {
|
|
|
552
606
|
const pedidoId = this.getNodeParameter("pedidoId", i).trim();
|
|
553
607
|
const observacoes = this.getNodeParameter("observacoes", i, "");
|
|
554
608
|
const agendadoPara = this.getNodeParameter("agendadoPara", i, "");
|
|
609
|
+
const tipoEntregaNova = this.getNodeParameter("tipoEntregaNova", i, "").trim();
|
|
610
|
+
const enderecoEntregaNovo = asObject(this.getNodeParameter("enderecoEntregaNovo", i, "{}"));
|
|
555
611
|
const adicionar = asArray(this.getNodeParameter("adicionarItens", i, "[]"));
|
|
556
612
|
const remover = parseIds(this.getNodeParameter("removerItemIds", i, ""));
|
|
557
613
|
const alterar = asArray(this.getNodeParameter("alterarItens", i, "[]"));
|
|
@@ -563,6 +619,11 @@ class Nucleo {
|
|
|
563
619
|
b.observacoes = observacoes.trim();
|
|
564
620
|
if (agendadoPara.trim())
|
|
565
621
|
b.agendado_para = agendadoPara.trim();
|
|
622
|
+
if (tipoEntregaNova)
|
|
623
|
+
b.tipo_entrega = tipoEntregaNova;
|
|
624
|
+
if (enderecoEntregaNovo && Object.keys(enderecoEntregaNovo).length) {
|
|
625
|
+
b.endereco_entrega = enderecoEntregaNovo;
|
|
626
|
+
}
|
|
566
627
|
if (adicionar.length)
|
|
567
628
|
b.adicionar_itens = adicionar;
|
|
568
629
|
if (remover.length)
|
|
@@ -599,7 +660,9 @@ class Nucleo {
|
|
|
599
660
|
const b = { resumo, resultado };
|
|
600
661
|
if (convTelefone)
|
|
601
662
|
b.telefone = convTelefone;
|
|
602
|
-
|
|
663
|
+
// pedido_id é opcional e a IA costuma alucinar ("nenhum", texto, UUID stale).
|
|
664
|
+
// Só envia se for UUID bem-formado — lixo nunca chega ao Núcleo.
|
|
665
|
+
if (convPedidoId && UUID_RE.test(convPedidoId))
|
|
603
666
|
b.pedido_id = convPedidoId;
|
|
604
667
|
idempotencyKey =
|
|
605
668
|
this.getNodeParameter("convIdempotencyKey", i, "").trim() || undefined;
|
|
@@ -613,38 +676,54 @@ class Nucleo {
|
|
|
613
676
|
});
|
|
614
677
|
}
|
|
615
678
|
// ---- assina e envia (HMAC v1) ----
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
679
|
+
const sendSigned = async (rawBody) => {
|
|
680
|
+
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
681
|
+
const nonce = (0, node_crypto_1.randomUUID)();
|
|
682
|
+
const canonical = canonicalString(method, path, timestamp, nonce, rawBody);
|
|
683
|
+
const signature = sign(signingSecret, canonical);
|
|
684
|
+
const headers = {
|
|
685
|
+
[HEADERS.key]: tokenId,
|
|
686
|
+
[HEADERS.timestamp]: timestamp,
|
|
687
|
+
[HEADERS.nonce]: nonce,
|
|
688
|
+
[HEADERS.signature]: signature,
|
|
689
|
+
};
|
|
690
|
+
if (rawBody)
|
|
691
|
+
headers["content-type"] = "application/json";
|
|
692
|
+
if (idempotencyKey)
|
|
693
|
+
headers["idempotency-key"] = idempotencyKey;
|
|
694
|
+
const res = await this.helpers.httpRequest({
|
|
695
|
+
method,
|
|
696
|
+
url: baseUrl + path,
|
|
697
|
+
headers,
|
|
698
|
+
body: rawBody || undefined,
|
|
699
|
+
json: false,
|
|
700
|
+
returnFullResponse: true,
|
|
701
|
+
ignoreHttpStatusErrors: true,
|
|
702
|
+
});
|
|
703
|
+
const status = res.statusCode;
|
|
704
|
+
const rawRes = res.body;
|
|
705
|
+
let payload;
|
|
706
|
+
try {
|
|
707
|
+
payload = typeof rawRes === "string" ? JSON.parse(rawRes) : rawRes;
|
|
708
|
+
}
|
|
709
|
+
catch {
|
|
710
|
+
payload = { raw: rawRes };
|
|
711
|
+
}
|
|
712
|
+
return { status, payload };
|
|
626
713
|
};
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
const status = res.statusCode;
|
|
641
|
-
const rawRes = res.body;
|
|
642
|
-
let payload;
|
|
643
|
-
try {
|
|
644
|
-
payload = typeof rawRes === "string" ? JSON.parse(rawRes) : rawRes;
|
|
645
|
-
}
|
|
646
|
-
catch {
|
|
647
|
-
payload = { raw: rawRes };
|
|
714
|
+
let rawBody = bodyObj === undefined ? "" : JSON.stringify(bodyObj);
|
|
715
|
+
let { status, payload } = await sendSigned(rawBody);
|
|
716
|
+
// Registrar conversa nunca deve falhar por causa de um pedido_id inválido
|
|
717
|
+
// (a IA pode mandar um UUID que não existe no tenant). Se o Núcleo recusar
|
|
718
|
+
// o pedido, reenvia sem ele — a conversa ainda é registrada.
|
|
719
|
+
if (status === 404 &&
|
|
720
|
+
resource === "conversa" &&
|
|
721
|
+
operation === "registrar" &&
|
|
722
|
+
bodyObj &&
|
|
723
|
+
bodyObj.pedido_id) {
|
|
724
|
+
delete bodyObj.pedido_id;
|
|
725
|
+
rawBody = JSON.stringify(bodyObj);
|
|
726
|
+
({ status, payload } = await sendSigned(rawBody));
|
|
648
727
|
}
|
|
649
728
|
if (status >= 400) {
|
|
650
729
|
const msg = (payload && payload.error) || `Núcleo respondeu HTTP ${status}.`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wondai/n8n-nodes-nucleo",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Node n8n para o Núcleo Wondai — atendimento de IA (cliente, catálogo fuzzy, pedidos) com assinatura HMAC v1. Tenant vem do token (a parede, ADR-013).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n-community-node-package",
|