n8n-nodes-atendix 1.3.0 → 1.3.2
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.
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* consumer_key/secret agora são campos de credencial (não env vars)
|
|
2
|
+
/* Arquivo: n8n-nodes-atendix/credentials/TrayApiAuto.credentials.ts
|
|
3
|
+
*
|
|
4
|
+
* Credenciais Tray API - Autenticação Automática
|
|
5
|
+
* Ajustado para evitar conflitos de nomes e erros de tipagem (unknown).
|
|
7
6
|
*/
|
|
8
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
8
|
exports.TrayApiAuto = void 0;
|
|
10
|
-
|
|
9
|
+
const CONSUMER_KEY = process.env.TRAY_CONSUMER_KEY;
|
|
10
|
+
const CONSUMER_SECRET = process.env.TRAY_CONSUMER_SECRET;
|
|
11
11
|
class TrayApiAuto {
|
|
12
12
|
constructor() {
|
|
13
13
|
this.name = 'trayApiAuto';
|
|
@@ -19,132 +19,150 @@ class TrayApiAuto {
|
|
|
19
19
|
this.documentationUrl = 'https://developers.tray.com.br/docs/';
|
|
20
20
|
this.properties = [
|
|
21
21
|
{
|
|
22
|
-
displayName: '
|
|
23
|
-
name: '
|
|
22
|
+
displayName: 'API Address',
|
|
23
|
+
name: 'apiAddress',
|
|
24
24
|
type: 'string',
|
|
25
|
-
default: '',
|
|
25
|
+
default: 'https://1225878.commercesuite.com.br/web_api',
|
|
26
26
|
required: true,
|
|
27
|
-
|
|
28
|
-
description: 'ID da sua loja Tray. A URL base (https://{storeId}.commercesuite.com.br/web_api) é gerada automaticamente.',
|
|
27
|
+
description: 'Endereço base da API Tray. Formato: https://{storeId}.commercesuite.com.br/web_api',
|
|
29
28
|
},
|
|
30
29
|
{
|
|
31
|
-
displayName: '
|
|
32
|
-
name: '
|
|
30
|
+
displayName: 'Authorization Code',
|
|
31
|
+
name: 'authCode',
|
|
33
32
|
type: 'string',
|
|
33
|
+
typeOptions: {
|
|
34
|
+
password: true,
|
|
35
|
+
},
|
|
34
36
|
default: '',
|
|
35
37
|
required: true,
|
|
36
|
-
|
|
38
|
+
placeholder: 'a9777f8cdfe4cf41b0a72c295adef1f9c43a093052c9e6dd98d1e629a09f13f3',
|
|
39
|
+
description: 'Código de autorização único da loja (fornecido pela Tray)',
|
|
37
40
|
},
|
|
38
41
|
{
|
|
39
|
-
displayName: '
|
|
40
|
-
name: '
|
|
41
|
-
type: '
|
|
42
|
-
typeOptions: { password: true },
|
|
42
|
+
displayName: 'Access Token',
|
|
43
|
+
name: 'accessToken',
|
|
44
|
+
type: 'hidden',
|
|
43
45
|
default: '',
|
|
44
|
-
required: true,
|
|
45
|
-
description: 'Consumer Secret fornecido pela Tray no painel de apps',
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
|
-
displayName: '
|
|
49
|
-
name: '
|
|
50
|
-
type: '
|
|
51
|
-
|
|
48
|
+
displayName: 'Refresh Token',
|
|
49
|
+
name: 'refreshToken',
|
|
50
|
+
type: 'hidden',
|
|
51
|
+
default: '',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
displayName: 'API Host',
|
|
55
|
+
name: 'apiHost',
|
|
56
|
+
type: 'hidden',
|
|
57
|
+
default: '',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
displayName: 'Token Expiration',
|
|
61
|
+
name: 'tokenExpiration',
|
|
62
|
+
type: 'hidden',
|
|
63
|
+
default: '',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
displayName: 'Store ID',
|
|
67
|
+
name: 'storeId',
|
|
68
|
+
type: 'hidden',
|
|
52
69
|
default: '',
|
|
53
|
-
required: true,
|
|
54
|
-
placeholder: 'a9777f8cdfe4cf41b0a72c295adef1f9c43a093052c9e6dd98d1e629a09f13f3',
|
|
55
|
-
description: 'Código de autorização único da loja (fornecido pela Tray)',
|
|
56
70
|
},
|
|
57
|
-
// Campos ocultos — cache interno de token
|
|
58
|
-
{ displayName: 'Access Token', name: 'accessToken', type: 'hidden', default: '' },
|
|
59
|
-
{ displayName: 'Refresh Token', name: 'refreshToken', type: 'hidden', default: '' },
|
|
60
|
-
{ displayName: 'Token Expiration',name: 'tokenExpiration',type: 'hidden', default: '' },
|
|
61
|
-
{ displayName: 'API Host', name: 'apiHost', type: 'hidden', default: '' },
|
|
62
71
|
];
|
|
72
|
+
// O segredo para o n8n injetar o token em todas as chamadas
|
|
63
73
|
this.authenticate = {
|
|
64
74
|
type: 'generic',
|
|
65
75
|
properties: {
|
|
66
|
-
qs: {
|
|
76
|
+
qs: {
|
|
77
|
+
access_token: '={{$credentials.accessToken}}',
|
|
78
|
+
},
|
|
67
79
|
},
|
|
68
80
|
};
|
|
69
81
|
this.test = {
|
|
70
82
|
request: {
|
|
71
|
-
|
|
83
|
+
// Usamos o apiAddress direto para o teste não quebrar se o apiHost estiver vazio
|
|
84
|
+
baseURL: '={{$credentials.apiAddress}}',
|
|
72
85
|
url: '/auth',
|
|
73
86
|
method: 'POST',
|
|
74
87
|
body: {
|
|
75
|
-
consumer_key:
|
|
76
|
-
consumer_secret:
|
|
77
|
-
code:
|
|
88
|
+
consumer_key: process.env.TRAY_CONSUMER_KEY,
|
|
89
|
+
consumer_secret: process.env.TRAY_CONSUMER_SECRET,
|
|
90
|
+
code: '={{$credentials.authCode}}',
|
|
78
91
|
},
|
|
79
92
|
},
|
|
80
93
|
};
|
|
81
94
|
}
|
|
82
|
-
|
|
83
|
-
_buildBaseUrl(credentials) {
|
|
84
|
-
const storeId = (credentials.storeId || '').toString().trim();
|
|
85
|
-
if (!storeId) throw new Error('[Atendix] Store ID não configurado nas credenciais');
|
|
86
|
-
// v1.3.0 FIX: URL correta — api.tray.com.br não existe
|
|
87
|
-
return `https://${storeId}.commercesuite.com.br/web_api`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
95
|
async preAuthentication(credentials) {
|
|
91
96
|
const now = new Date();
|
|
92
97
|
const tokenExpiration = credentials.tokenExpiration
|
|
93
98
|
? new Date(credentials.tokenExpiration)
|
|
94
99
|
: null;
|
|
95
|
-
|
|
96
100
|
if (credentials.accessToken && tokenExpiration && tokenExpiration > now) {
|
|
97
|
-
return credentials;
|
|
101
|
+
return credentials;
|
|
98
102
|
}
|
|
99
103
|
if (credentials.refreshToken && tokenExpiration && tokenExpiration <= now) {
|
|
100
104
|
return await this._refreshTokenFlow(credentials);
|
|
101
105
|
}
|
|
102
106
|
return await this._authenticateFlow(credentials);
|
|
103
107
|
}
|
|
104
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Renomeado para evitar conflito com a propriedade 'authenticate' da interface
|
|
110
|
+
*/
|
|
105
111
|
async _authenticateFlow(credentials) {
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
112
|
+
const apiAddress = credentials.apiAddress.replace(/\/$/, '');
|
|
113
|
+
const authCode = credentials.authCode;
|
|
114
|
+
// Pega as chaves direto do processo para garantir que não estão vazias
|
|
115
|
+
const consumerKey = process.env.TRAY_CONSUMER_KEY;
|
|
116
|
+
const consumerSecret = process.env.TRAY_CONSUMER_SECRET;
|
|
117
|
+
console.log(` Tentando autenticar na URL: ${apiAddress}/auth`);
|
|
118
|
+
console.log(` Usando Key: ${consumerKey ? 'Preenchida' : 'VAZIA!'}`);
|
|
119
|
+
try {
|
|
120
|
+
const response = await fetch(`${apiAddress}/auth`, {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
headers: { 'Content-Type': 'application/json' },
|
|
123
|
+
body: JSON.stringify({
|
|
124
|
+
consumer_key: consumerKey,
|
|
125
|
+
consumer_secret: consumerSecret,
|
|
126
|
+
code: authCode,
|
|
127
|
+
}),
|
|
128
|
+
});
|
|
129
|
+
const data = (await response.json());
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
console.error(' Erro retornado pela Tray:', data);
|
|
132
|
+
throw new Error(data.message || 'Erro desconhecido na Tray');
|
|
133
|
+
}
|
|
134
|
+
console.log(' Autenticação com a Tray teve SUCESSO!');
|
|
135
|
+
return {
|
|
136
|
+
...credentials,
|
|
137
|
+
accessToken: data.access_token,
|
|
138
|
+
refreshToken: data.refresh_token,
|
|
139
|
+
apiHost: data.api_host, // Geralmente vem algo como "https://1225878.commercesuite.com.br"
|
|
140
|
+
storeId: data.store_id,
|
|
141
|
+
tokenExpiration: data.date_expiration_access_token,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.error(' Erro Crítico no Flow de Autenticação:', error.message);
|
|
146
|
+
throw new Error(`Falha ao autenticar na Tray: ${error.message}`);
|
|
123
147
|
}
|
|
124
|
-
|
|
125
|
-
console.log('[Atendix v1.3.0] Autenticação OK ✓');
|
|
126
|
-
return {
|
|
127
|
-
...credentials,
|
|
128
|
-
accessToken: data.access_token,
|
|
129
|
-
refreshToken: data.refresh_token,
|
|
130
|
-
apiHost: data.api_host,
|
|
131
|
-
tokenExpiration: data.date_expiration_access_token,
|
|
132
|
-
};
|
|
133
148
|
}
|
|
134
|
-
|
|
135
149
|
async _refreshTokenFlow(credentials) {
|
|
136
|
-
const
|
|
150
|
+
const apiAddress = credentials.apiAddress;
|
|
151
|
+
const refreshToken = credentials.refreshToken;
|
|
137
152
|
try {
|
|
138
|
-
const response = await fetch(`${
|
|
139
|
-
const data = await response.json();
|
|
140
|
-
if (!response.ok)
|
|
153
|
+
const response = await fetch(`${apiAddress}/auth?refresh_token=${refreshToken}`);
|
|
154
|
+
const data = (await response.json());
|
|
155
|
+
if (!response.ok) {
|
|
156
|
+
return await this._authenticateFlow(credentials);
|
|
157
|
+
}
|
|
141
158
|
return {
|
|
142
159
|
...credentials,
|
|
143
|
-
accessToken:
|
|
144
|
-
refreshToken:
|
|
160
|
+
accessToken: data.access_token,
|
|
161
|
+
refreshToken: data.refresh_token,
|
|
145
162
|
tokenExpiration: data.date_expiration_access_token,
|
|
146
163
|
};
|
|
147
|
-
}
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
148
166
|
return await this._authenticateFlow(credentials);
|
|
149
167
|
}
|
|
150
168
|
}
|
|
@@ -1,379 +1,335 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* n8n-nodes-atendix
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* - Recursos: Pedido, Nota Fiscal, Cliente, Rastreamento, Produto
|
|
3
|
+
* Arquivo: n8n-nodes-atendix/nodes/Atendix/Atendix.node.ts
|
|
4
|
+
*
|
|
5
|
+
* Node oficial Atendix - Tray Commerce
|
|
6
|
+
* Versão 1.2.0 - Licenciamento SaaS + Full Operations
|
|
8
7
|
*/
|
|
9
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
9
|
exports.Atendix = void 0;
|
|
11
10
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
12
|
-
|
|
13
11
|
const wait = (ms) => new Promise(res => setTimeout(res, ms));
|
|
14
|
-
|
|
15
12
|
class Atendix {
|
|
16
13
|
constructor() {
|
|
17
14
|
this.description = {
|
|
18
15
|
displayName: 'Atendix - Tray API',
|
|
19
16
|
name: 'atendix',
|
|
20
|
-
icon: {
|
|
17
|
+
icon: {
|
|
18
|
+
light: 'file:tray.svg',
|
|
19
|
+
dark: 'file:tray.svg',
|
|
20
|
+
},
|
|
21
21
|
group: ['transform'],
|
|
22
22
|
version: 1,
|
|
23
23
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
|
24
|
-
description: 'Integração
|
|
25
|
-
defaults: {
|
|
24
|
+
description: 'Integração com a plataforma Tray Commerce',
|
|
25
|
+
defaults: {
|
|
26
|
+
name: 'Atendix - Tray API',
|
|
27
|
+
},
|
|
26
28
|
inputs: ['main'],
|
|
27
29
|
outputs: ['main'],
|
|
28
|
-
credentials: [
|
|
30
|
+
credentials: [
|
|
31
|
+
{
|
|
32
|
+
name: 'trayApiAuto',
|
|
33
|
+
required: true,
|
|
34
|
+
displayName: 'Atendix - Tray API',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
29
37
|
properties: [
|
|
30
|
-
//
|
|
38
|
+
// ==================== RESOURCE SELECTOR ====================
|
|
31
39
|
{
|
|
32
|
-
displayName: 'Resource',
|
|
40
|
+
displayName: 'Resource',
|
|
41
|
+
name: 'resource',
|
|
42
|
+
type: 'options',
|
|
33
43
|
noDataExpression: true,
|
|
34
44
|
options: [
|
|
35
|
-
{ name: 'Pedido',
|
|
36
|
-
{ name: '
|
|
37
|
-
{ name: '
|
|
38
|
-
{ name: 'Rastreamento', value: 'tracking' },
|
|
39
|
-
{ name: 'Produto', value: 'product' },
|
|
45
|
+
{ name: 'Pedido', value: 'order' },
|
|
46
|
+
{ name: 'Cliente', value: 'customer' },
|
|
47
|
+
{ name: 'Produto', value: 'product' },
|
|
40
48
|
],
|
|
41
49
|
default: 'order',
|
|
42
50
|
},
|
|
43
|
-
|
|
44
|
-
// ===== PEDIDO — OPERATIONS =====
|
|
51
|
+
// ==================== PEDIDOS - OPERATIONS ====================
|
|
45
52
|
{
|
|
46
|
-
displayName: 'Operation',
|
|
53
|
+
displayName: 'Operation',
|
|
54
|
+
name: 'operation',
|
|
55
|
+
type: 'options',
|
|
47
56
|
noDataExpression: true,
|
|
48
57
|
displayOptions: { show: { resource: ['order'] } },
|
|
49
58
|
options: [
|
|
50
|
-
{
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
{
|
|
60
|
+
name: 'Buscar Pedido',
|
|
61
|
+
value: 'get',
|
|
62
|
+
description: 'Busca um pedido específico por ID',
|
|
63
|
+
action: 'Buscar um pedido',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'Listar Pedidos',
|
|
67
|
+
value: 'list',
|
|
68
|
+
description: 'Lista pedidos com filtros opcionais',
|
|
69
|
+
action: 'Listar pedidos',
|
|
70
|
+
},
|
|
54
71
|
],
|
|
55
72
|
default: 'get',
|
|
56
73
|
},
|
|
57
74
|
{
|
|
58
|
-
displayName: 'ID do Pedido',
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
displayOptions: { show: { resource: ['order'], operation: ['updateStatus'] } },
|
|
65
|
-
options: [
|
|
66
|
-
{ name: 'Aprovado', value: 'approved' },
|
|
67
|
-
{ name: 'Em Preparação', value: 'preparing' },
|
|
68
|
-
{ name: 'Enviado', value: 'shipped' },
|
|
69
|
-
{ name: 'Entregue', value: 'delivered' },
|
|
70
|
-
{ name: 'Cancelado', value: 'cancelled' },
|
|
71
|
-
],
|
|
72
|
-
default: 'approved',
|
|
75
|
+
displayName: 'ID do Pedido',
|
|
76
|
+
name: 'orderId',
|
|
77
|
+
type: 'string',
|
|
78
|
+
required: true,
|
|
79
|
+
displayOptions: { show: { resource: ['order'], operation: ['get'] } },
|
|
80
|
+
default: '',
|
|
73
81
|
},
|
|
74
82
|
{
|
|
75
|
-
displayName: 'Filtros',
|
|
76
|
-
|
|
83
|
+
displayName: 'Filtros',
|
|
84
|
+
name: 'filters',
|
|
85
|
+
type: 'collection',
|
|
86
|
+
placeholder: 'Adicionar Filtro',
|
|
87
|
+
default: {},
|
|
77
88
|
displayOptions: { show: { resource: ['order'], operation: ['list'] } },
|
|
78
89
|
options: [
|
|
79
90
|
{
|
|
80
|
-
displayName: 'Status',
|
|
91
|
+
displayName: 'Status',
|
|
92
|
+
name: 'status',
|
|
93
|
+
type: 'options',
|
|
81
94
|
options: [
|
|
82
|
-
{ name: 'Pendente',
|
|
83
|
-
{ name: 'Aprovado',
|
|
84
|
-
{ name: 'Enviado',
|
|
85
|
-
{ name: 'Entregue',
|
|
95
|
+
{ name: 'Pendente', value: 'pending' },
|
|
96
|
+
{ name: 'Aprovado', value: 'approved' },
|
|
97
|
+
{ name: 'Enviado', value: 'shipped' },
|
|
98
|
+
{ name: 'Entregue', value: 'delivered' },
|
|
86
99
|
{ name: 'Cancelado', value: 'cancelled' },
|
|
87
100
|
],
|
|
101
|
+
default: '',
|
|
88
102
|
},
|
|
89
103
|
{ displayName: 'Limite', name: 'limit', type: 'number', default: 50 },
|
|
90
|
-
{ displayName: 'Página', name: 'page', type: 'number', default: 1 },
|
|
91
|
-
{ displayName: 'Data Início (YYYY-MM-DD)', name: 'dateFrom', type: 'string', default: '' },
|
|
92
|
-
{ displayName: 'Data Fim (YYYY-MM-DD)', name: 'dateTo', type: 'string', default: '' },
|
|
93
104
|
],
|
|
94
105
|
},
|
|
95
|
-
|
|
96
|
-
// ===== NOTA FISCAL — OPERATIONS =====
|
|
97
|
-
// v1.3.0 FIX: endpoints corretos são /orders/:id/invoices
|
|
98
|
-
{
|
|
99
|
-
displayName: 'Operation', name: 'operation', type: 'options',
|
|
100
|
-
noDataExpression: true,
|
|
101
|
-
displayOptions: { show: { resource: ['invoice'] } },
|
|
102
|
-
options: [
|
|
103
|
-
{ name: 'Listar NFs do Pedido', value: 'list', action: 'GET /orders/:id/invoices' },
|
|
104
|
-
{ name: 'Buscar NF por ID', value: 'get', action: 'GET /orders/:id/invoices/:inv_id' },
|
|
105
|
-
{ name: 'Cadastrar NF', value: 'create', action: 'POST /invoices' },
|
|
106
|
-
],
|
|
107
|
-
default: 'list',
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
displayName: 'ID do Pedido', name: 'orderId', type: 'string', required: true,
|
|
111
|
-
displayOptions: { show: { resource: ['invoice'], operation: ['list', 'get'] } },
|
|
112
|
-
default: '', placeholder: '9847',
|
|
113
|
-
description: 'ID do pedido Tray para buscar as NFs',
|
|
114
|
-
},
|
|
106
|
+
// ==================== CLIENTES - OPERATIONS ====================
|
|
115
107
|
{
|
|
116
|
-
displayName: '
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
displayName: 'Dados da NF (JSON)', name: 'invoiceData', type: 'json',
|
|
122
|
-
displayOptions: { show: { resource: ['invoice'], operation: ['create'] } },
|
|
123
|
-
default: '{\n "order_id": "",\n "issue_date": "",\n "number": "",\n "serie": "",\n "value": "",\n "xml": "",\n "key": "",\n "link": ""\n}',
|
|
124
|
-
},
|
|
125
|
-
|
|
126
|
-
// ===== CLIENTES — OPERATIONS =====
|
|
127
|
-
{
|
|
128
|
-
displayName: 'Operation', name: 'operation', type: 'options',
|
|
108
|
+
displayName: 'Operation',
|
|
109
|
+
name: 'operation',
|
|
110
|
+
type: 'options',
|
|
129
111
|
noDataExpression: true,
|
|
130
112
|
displayOptions: { show: { resource: ['customer'] } },
|
|
131
113
|
options: [
|
|
132
|
-
{ name: 'Buscar
|
|
133
|
-
{ name: '
|
|
134
|
-
{ name: 'Buscar por Tel/E-mail', value: 'search', action: 'Buscar por telefone ou email' },
|
|
114
|
+
{ name: 'Buscar Cliente', value: 'get', action: 'Buscar um cliente' },
|
|
115
|
+
{ name: 'Criar/Atualizar Cliente', value: 'upsert', action: 'Criar ou atualizar cliente' },
|
|
135
116
|
],
|
|
136
117
|
default: 'get',
|
|
137
118
|
},
|
|
138
119
|
{
|
|
139
|
-
displayName: '
|
|
120
|
+
displayName: 'Buscar Por',
|
|
121
|
+
name: 'searchType',
|
|
122
|
+
type: 'options',
|
|
140
123
|
displayOptions: { show: { resource: ['customer'], operation: ['get'] } },
|
|
141
|
-
default: '',
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
displayName: 'Buscar Por', name: 'searchType', type: 'options',
|
|
145
|
-
displayOptions: { show: { resource: ['customer'], operation: ['search'] } },
|
|
146
124
|
options: [
|
|
147
|
-
{ name: 'E-mail',
|
|
148
|
-
{ name: '
|
|
149
|
-
{ name: 'CPF/CNPJ', value: 'cpf_cnpj'},
|
|
125
|
+
{ name: 'E-mail', value: 'email' },
|
|
126
|
+
{ name: 'CPF/CNPJ', value: 'cpf_cnpj' },
|
|
150
127
|
],
|
|
151
128
|
default: 'email',
|
|
152
129
|
},
|
|
153
130
|
{
|
|
154
|
-
displayName: 'Valor
|
|
155
|
-
|
|
131
|
+
displayName: 'Valor',
|
|
132
|
+
name: 'searchValue',
|
|
133
|
+
type: 'string',
|
|
134
|
+
displayOptions: { show: { resource: ['customer'], operation: ['get'] } },
|
|
156
135
|
default: '',
|
|
157
136
|
},
|
|
137
|
+
// ==================== PRODUTOS - OPERATIONS ====================
|
|
158
138
|
{
|
|
159
|
-
displayName: '
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
options: [
|
|
163
|
-
{ displayName: 'Limite', name: 'limit', type: 'number', default: 50 },
|
|
164
|
-
{ displayName: 'Página', name: 'page', type: 'number', default: 1 },
|
|
165
|
-
],
|
|
166
|
-
},
|
|
167
|
-
|
|
168
|
-
// ===== RASTREAMENTO — OPERATIONS =====
|
|
169
|
-
{
|
|
170
|
-
displayName: 'Operation', name: 'operation', type: 'options',
|
|
171
|
-
noDataExpression: true,
|
|
172
|
-
displayOptions: { show: { resource: ['tracking'] } },
|
|
173
|
-
options: [
|
|
174
|
-
{ name: 'Código de Rastreio', value: 'getTracking', action: 'Obter código de rastreio do pedido' },
|
|
175
|
-
{ name: 'Formas de Envio', value: 'getShipping', action: 'Listar formas de envio disponíveis' },
|
|
176
|
-
],
|
|
177
|
-
default: 'getTracking',
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
displayName: 'ID do Pedido', name: 'orderId', type: 'string', required: true,
|
|
181
|
-
displayOptions: { show: { resource: ['tracking'], operation: ['getTracking'] } },
|
|
182
|
-
default: '', placeholder: '9847',
|
|
183
|
-
},
|
|
184
|
-
|
|
185
|
-
// ===== PRODUTO — OPERATIONS =====
|
|
186
|
-
{
|
|
187
|
-
displayName: 'Operation', name: 'operation', type: 'options',
|
|
139
|
+
displayName: 'Operation',
|
|
140
|
+
name: 'operation',
|
|
141
|
+
type: 'options',
|
|
188
142
|
noDataExpression: true,
|
|
189
143
|
displayOptions: { show: { resource: ['product'] } },
|
|
190
144
|
options: [
|
|
191
|
-
{ name: 'Buscar Produto',
|
|
192
|
-
{ name: 'Atualizar Estoque', value: 'updateStock', action: 'Atualizar estoque'
|
|
145
|
+
{ name: 'Buscar Produto', value: 'get', action: 'Buscar um produto' },
|
|
146
|
+
{ name: 'Atualizar Estoque', value: 'updateStock', action: 'Atualizar estoque do produto' },
|
|
193
147
|
],
|
|
194
148
|
default: 'get',
|
|
195
149
|
},
|
|
196
150
|
{
|
|
197
|
-
displayName: 'Buscar Por',
|
|
151
|
+
displayName: 'Buscar Por',
|
|
152
|
+
name: 'searchType',
|
|
153
|
+
type: 'options',
|
|
198
154
|
displayOptions: { show: { resource: ['product'], operation: ['get'] } },
|
|
199
155
|
options: [
|
|
200
|
-
{ name: 'ID do Produto', value: 'id'
|
|
201
|
-
{ name: 'SKU',
|
|
156
|
+
{ name: 'ID do Produto', value: 'id' },
|
|
157
|
+
{ name: 'SKU', value: 'sku' },
|
|
202
158
|
],
|
|
203
159
|
default: 'id',
|
|
204
160
|
},
|
|
205
161
|
{
|
|
206
|
-
displayName: 'Valor',
|
|
162
|
+
displayName: 'Valor',
|
|
163
|
+
name: 'searchValue',
|
|
164
|
+
type: 'string',
|
|
207
165
|
displayOptions: { show: { resource: ['product'], operation: ['get'] } },
|
|
208
166
|
default: '',
|
|
209
167
|
},
|
|
210
168
|
],
|
|
211
169
|
};
|
|
212
170
|
}
|
|
213
|
-
|
|
214
171
|
async execute() {
|
|
215
|
-
|
|
172
|
+
var _a, _b;
|
|
173
|
+
const items = this.getInputData();
|
|
216
174
|
const returnData = [];
|
|
217
|
-
const resource
|
|
175
|
+
const resource = this.getNodeParameter('resource', 0);
|
|
218
176
|
const operation = this.getNodeParameter('operation', 0);
|
|
219
|
-
|
|
220
|
-
|
|
177
|
+
const atendixAuthToken = process.env.ATENDIX_AUTH_TOKEN;
|
|
178
|
+
if (!atendixAuthToken) {
|
|
179
|
+
console.error('❌ ATENDIX_AUTH_TOKEN não configurado nas variáveis de ambiente');
|
|
180
|
+
throw new Error('Configuração de validação Atendix incompleta. Entre em contato com o suporte.');
|
|
181
|
+
}
|
|
182
|
+
// 1. Get Credentials
|
|
221
183
|
const credentials = await this.getCredentials('trayApiAuto');
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
184
|
+
let baseUrl = (credentials.apiHost || credentials.apiAddress || '').trim().replace(/\/$/, '');
|
|
185
|
+
let licenseCheck;
|
|
186
|
+
// ==========================================
|
|
187
|
+
// 1.1 VALIDAÇÃO DE LICENÇA (SaaS GATEKEEPER)
|
|
188
|
+
// ==========================================
|
|
189
|
+
// Pegamos a URL base da loja para validar no seu servidor
|
|
190
|
+
const storeBaseUrl = baseUrl.replace('/web_api', '');
|
|
191
|
+
try {
|
|
192
|
+
licenseCheck = await this.helpers.httpRequest({
|
|
193
|
+
method: 'POST',
|
|
194
|
+
url: 'https://n8n.mariapinho.com.br/webhook/valida-atendix',
|
|
195
|
+
headers: {
|
|
196
|
+
'Content-Type': 'application/json',
|
|
197
|
+
'Authorization': atendixAuthToken,
|
|
198
|
+
},
|
|
199
|
+
body: { url: storeBaseUrl },
|
|
200
|
+
json: true,
|
|
201
|
+
});
|
|
202
|
+
console.error('LICENSE CHECK RESPONSE: ' + licenseCheck.autorizado);
|
|
203
|
+
if (!licenseCheck || licenseCheck.autorizado !== true) {
|
|
204
|
+
console.error('Resposta inválida do servidor de licenças:', licenseCheck);
|
|
205
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Sua licença Atendix para a loja ${storeBaseUrl} não está ativa ou não pode ser validada. Para liberar seu acesso e automatizar sua operação, acesse https://atendix.co e assine um plano agora mesmo!`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
console.error('ATENDIX_AUTH_TOKEN:', atendixAuthToken);
|
|
210
|
+
console.error('STORE BASE URL:', storeBaseUrl);
|
|
211
|
+
console.error('LICENSECHECK:', licenseCheck);
|
|
212
|
+
console.error('Erro HTTP licença:', (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status);
|
|
213
|
+
console.error('Body licença:', (_b = error === null || error === void 0 ? void 0 : error.response) === null || _b === void 0 ? void 0 : _b.data);
|
|
214
|
+
// Se for erro de acesso bloqueado, repassa. Se for erro de rede, avisa o usuário.
|
|
215
|
+
if (error instanceof n8n_workflow_1.NodeOperationError)
|
|
216
|
+
throw error;
|
|
217
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), "Erro crítico: Não foi possível validar a licença do Atendix no servidor MariaPinho.");
|
|
218
|
+
}
|
|
219
|
+
// 2. TOKEN MANAGER (Fallback)
|
|
220
|
+
let accessToken = credentials.accessToken;
|
|
221
|
+
if (!accessToken) {
|
|
222
|
+
try {
|
|
223
|
+
const authResponse = await this.helpers.httpRequest({
|
|
224
|
+
method: 'POST',
|
|
225
|
+
url: `${baseUrl}/auth`,
|
|
226
|
+
body: {
|
|
227
|
+
consumer_key: process.env.TRAY_CONSUMER_KEY,
|
|
228
|
+
consumer_secret: process.env.TRAY_CONSUMER_SECRET,
|
|
229
|
+
code: credentials.authCode,
|
|
230
|
+
},
|
|
231
|
+
json: true,
|
|
232
|
+
});
|
|
233
|
+
accessToken = authResponse.access_token;
|
|
234
|
+
}
|
|
235
|
+
catch (authError) {
|
|
236
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Falha na Autenticação Tray: ${authError.message}`);
|
|
237
|
+
}
|
|
229
238
|
}
|
|
230
|
-
|
|
231
|
-
// v1.3.0: URL derivada do store_id — nunca mais hardcoded
|
|
232
|
-
const baseUrl = `https://${storeId}.commercesuite.com.br/web_api`;
|
|
233
|
-
const accessToken = credentials.accessToken;
|
|
234
|
-
|
|
235
239
|
for (let i = 0; i < items.length; i++) {
|
|
236
240
|
try {
|
|
237
|
-
if (i > 0) await wait(500); // Rate-limit seguro: ~120 req/min (limite Tray: 180)
|
|
238
|
-
|
|
239
241
|
let responseData = {};
|
|
240
242
|
const qs = { access_token: accessToken };
|
|
241
|
-
|
|
242
|
-
// ===== PEDIDOS =====
|
|
243
|
+
// ==================== PEDIDOS ====================
|
|
243
244
|
if (resource === 'order') {
|
|
244
245
|
if (operation === 'get') {
|
|
245
246
|
const orderId = this.getNodeParameter('orderId', i);
|
|
247
|
+
if (i > 0) {
|
|
248
|
+
// Dá uma respirada de 500ms entre cada item da lista
|
|
249
|
+
// 500ms = 2 requisições por segundo = 120 por minuto (Seguro dentro dos 180)
|
|
250
|
+
await wait(500);
|
|
251
|
+
}
|
|
246
252
|
responseData = await this.helpers.httpRequest({
|
|
247
|
-
method: 'GET',
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
responseData = await this.helpers.httpRequest({
|
|
252
|
-
method: 'GET', url: `${baseUrl}/orders/${orderId}/complete`, qs, json: true,
|
|
253
|
-
});
|
|
254
|
-
} else if (operation === 'list') {
|
|
255
|
-
const filters = this.getNodeParameter('filters', i, {});
|
|
256
|
-
if (filters.status) qs.status = filters.status;
|
|
257
|
-
if (filters.limit) qs.limit = filters.limit;
|
|
258
|
-
if (filters.page) qs.page = filters.page;
|
|
259
|
-
if (filters.dateFrom) qs.date_from = filters.dateFrom;
|
|
260
|
-
if (filters.dateTo) qs.date_to = filters.dateTo;
|
|
261
|
-
responseData = await this.helpers.httpRequest({
|
|
262
|
-
method: 'GET', url: `${baseUrl}/orders`, qs, json: true,
|
|
263
|
-
});
|
|
264
|
-
} else if (operation === 'updateStatus') {
|
|
265
|
-
const orderId = this.getNodeParameter('orderId', i);
|
|
266
|
-
const orderStatus = this.getNodeParameter('orderStatus', i);
|
|
267
|
-
responseData = await this.helpers.httpRequest({
|
|
268
|
-
method: 'PUT', url: `${baseUrl}/orders/${orderId}`,
|
|
269
|
-
qs, body: { status: orderStatus }, json: true,
|
|
253
|
+
method: 'GET',
|
|
254
|
+
url: `${baseUrl}/orders/${orderId}`,
|
|
255
|
+
qs,
|
|
256
|
+
json: true,
|
|
270
257
|
});
|
|
271
258
|
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// ===== NOTA FISCAL =====
|
|
275
|
-
// v1.3.0 FIX CRÍTICO: endpoints corretos são /orders/:id/invoices
|
|
276
|
-
else if (resource === 'invoice') {
|
|
277
259
|
if (operation === 'list') {
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
method: 'GET', url: `${baseUrl}/orders/${orderId}/invoices/${invoiceId}`, qs, json: true,
|
|
289
|
-
});
|
|
290
|
-
} else if (operation === 'create') {
|
|
291
|
-
const invoiceData = JSON.parse(this.getNodeParameter('invoiceData', i));
|
|
260
|
+
const filters = this.getNodeParameter('filters', i, {});
|
|
261
|
+
if (filters.status)
|
|
262
|
+
qs.status = filters.status;
|
|
263
|
+
if (filters.limit)
|
|
264
|
+
qs.limit = filters.limit;
|
|
265
|
+
if (i > 0) {
|
|
266
|
+
// Dá uma respirada de 500ms entre cada item da lista
|
|
267
|
+
// 500ms = 2 requisições por segundo = 120 por minuto (Seguro dentro dos 180)
|
|
268
|
+
await wait(500);
|
|
269
|
+
}
|
|
292
270
|
responseData = await this.helpers.httpRequest({
|
|
293
|
-
method: '
|
|
271
|
+
method: 'GET',
|
|
272
|
+
url: `${baseUrl}/orders`,
|
|
273
|
+
qs,
|
|
274
|
+
json: true,
|
|
294
275
|
});
|
|
295
276
|
}
|
|
296
277
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
else if (resource === 'customer') {
|
|
278
|
+
// ==================== CLIENTES ====================
|
|
279
|
+
if (resource === 'customer') {
|
|
300
280
|
if (operation === 'get') {
|
|
301
|
-
const
|
|
302
|
-
responseData = await this.helpers.httpRequest({
|
|
303
|
-
method: 'GET', url: `${baseUrl}/customers/${customerId}`, qs, json: true,
|
|
304
|
-
});
|
|
305
|
-
} else if (operation === 'list') {
|
|
306
|
-
const filters = this.getNodeParameter('filters', i, {});
|
|
307
|
-
if (filters.limit) qs.limit = filters.limit;
|
|
308
|
-
if (filters.page) qs.page = filters.page;
|
|
309
|
-
responseData = await this.helpers.httpRequest({
|
|
310
|
-
method: 'GET', url: `${baseUrl}/customers`, qs, json: true,
|
|
311
|
-
});
|
|
312
|
-
} else if (operation === 'search') {
|
|
313
|
-
const searchType = this.getNodeParameter('searchType', i);
|
|
281
|
+
const searchType = this.getNodeParameter('searchType', i);
|
|
314
282
|
const searchValue = this.getNodeParameter('searchValue', i);
|
|
315
|
-
|
|
283
|
+
if (searchType === 'email')
|
|
284
|
+
qs.email = searchValue;
|
|
285
|
+
else
|
|
286
|
+
qs.cpf_cnpj = searchValue;
|
|
316
287
|
responseData = await this.helpers.httpRequest({
|
|
317
|
-
method: 'GET',
|
|
288
|
+
method: 'GET',
|
|
289
|
+
url: `${baseUrl}/customers`,
|
|
290
|
+
qs,
|
|
291
|
+
json: true,
|
|
318
292
|
});
|
|
319
293
|
}
|
|
294
|
+
// Nota da Gerente: Se for adicionar UPSERT aqui, o código deve ser incluído abaixo
|
|
320
295
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
else if (resource === 'tracking') {
|
|
324
|
-
if (operation === 'getTracking') {
|
|
325
|
-
const orderId = this.getNodeParameter('orderId', i);
|
|
326
|
-
const orderRaw = await this.helpers.httpRequest({
|
|
327
|
-
method: 'GET', url: `${baseUrl}/orders/${orderId}`, qs, json: true,
|
|
328
|
-
});
|
|
329
|
-
const order = (orderRaw.Order || orderRaw);
|
|
330
|
-
responseData = {
|
|
331
|
-
order_id: orderId,
|
|
332
|
-
tracking_code: order.shipping_tracking_code || null,
|
|
333
|
-
shipping_carrier: order.shipping_carrier || null,
|
|
334
|
-
shipping_method: order.shipping_method_name || null,
|
|
335
|
-
};
|
|
336
|
-
} else if (operation === 'getShipping') {
|
|
337
|
-
responseData = await this.helpers.httpRequest({
|
|
338
|
-
method: 'GET', url: `${baseUrl}/shipping`, qs, json: true,
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// ===== PRODUTO =====
|
|
344
|
-
else if (resource === 'product') {
|
|
296
|
+
// ==================== PRODUTOS ====================
|
|
297
|
+
if (resource === 'product') {
|
|
345
298
|
if (operation === 'get') {
|
|
346
|
-
const searchType
|
|
299
|
+
const searchType = this.getNodeParameter('searchType', i);
|
|
347
300
|
const searchValue = this.getNodeParameter('searchValue', i);
|
|
348
301
|
if (searchType === 'id') {
|
|
349
302
|
responseData = await this.helpers.httpRequest({
|
|
350
|
-
method: 'GET',
|
|
303
|
+
method: 'GET',
|
|
304
|
+
url: `${baseUrl}/products/${searchValue}`,
|
|
305
|
+
qs,
|
|
306
|
+
json: true,
|
|
351
307
|
});
|
|
352
|
-
}
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
353
310
|
qs.sku = searchValue;
|
|
354
311
|
responseData = await this.helpers.httpRequest({
|
|
355
|
-
method: 'GET',
|
|
312
|
+
method: 'GET',
|
|
313
|
+
url: `${baseUrl}/products`,
|
|
314
|
+
qs,
|
|
315
|
+
json: true,
|
|
356
316
|
});
|
|
357
317
|
}
|
|
358
318
|
}
|
|
319
|
+
// Nota da Gerente: Se for adicionar UPDATE_STOCK aqui, o código deve ser incluído abaixo
|
|
359
320
|
}
|
|
360
|
-
|
|
361
|
-
const executionData = this.helpers.constructExecutionMetaData(
|
|
362
|
-
this.helpers.returnJsonArray(responseData),
|
|
363
|
-
{ itemData: { item: i } },
|
|
364
|
-
);
|
|
321
|
+
const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(responseData), { itemData: { item: i } });
|
|
365
322
|
returnData.push(...executionData);
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
369
326
|
if (this.continueOnFail()) {
|
|
370
|
-
returnData.push({ json: { error:
|
|
327
|
+
returnData.push({ json: { error: errorMessage }, pairedItem: { item: i } });
|
|
371
328
|
continue;
|
|
372
329
|
}
|
|
373
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(),
|
|
330
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), errorMessage, { itemIndex: i });
|
|
374
331
|
}
|
|
375
332
|
}
|
|
376
|
-
|
|
377
333
|
return [returnData];
|
|
378
334
|
}
|
|
379
335
|
}
|