n8n-nodes-atendix 1.3.1 → 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
- * n8n-nodes-atendix — Credenciais Tray Commerce
4
- * v1.3.1 Reverte UX para API Address + Auth Code apenas
5
- * consumer_key/secret são do Atendix (process.env no servidor)
6
- * URL padrão corrigida: {store}.commercesuite.com.br/web_api
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';
@@ -22,122 +22,147 @@ class TrayApiAuto {
22
22
  displayName: 'API Address',
23
23
  name: 'apiAddress',
24
24
  type: 'string',
25
- default: '',
25
+ default: 'https://1225878.commercesuite.com.br/web_api',
26
26
  required: true,
27
- placeholder: 'https://1225878.commercesuite.com.br/web_api',
28
- description: 'URL base da API da sua loja Tray. Formato: https://{storeId}.commercesuite.com.br/web_api',
27
+ description: 'Endereço base da API Tray. Formato: https://{storeId}.commercesuite.com.br/web_api',
29
28
  },
30
29
  {
31
30
  displayName: 'Authorization Code',
32
31
  name: 'authCode',
33
32
  type: 'string',
34
- typeOptions: { password: true },
33
+ typeOptions: {
34
+ password: true,
35
+ },
35
36
  default: '',
36
37
  required: true,
37
38
  placeholder: 'a9777f8cdfe4cf41b0a72c295adef1f9c43a093052c9e6dd98d1e629a09f13f3',
38
39
  description: 'Código de autorização único da loja (fornecido pela Tray)',
39
40
  },
40
- // Campos ocultos — cache interno de token
41
- { displayName: 'Access Token', name: 'accessToken', type: 'hidden', default: '' },
42
- { displayName: 'Refresh Token', name: 'refreshToken', type: 'hidden', default: '' },
43
- { displayName: 'Token Expiration',name: 'tokenExpiration',type: 'hidden', default: '' },
44
- { displayName: 'API Host', name: 'apiHost', type: 'hidden', default: '' },
41
+ {
42
+ displayName: 'Access Token',
43
+ name: 'accessToken',
44
+ type: 'hidden',
45
+ default: '',
46
+ },
47
+ {
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',
69
+ default: '',
70
+ },
45
71
  ];
46
-
72
+ // O segredo para o n8n injetar o token em todas as chamadas
47
73
  this.authenticate = {
48
74
  type: 'generic',
49
75
  properties: {
50
- qs: { access_token: '={{$credentials.accessToken}}' },
76
+ qs: {
77
+ access_token: '={{$credentials.accessToken}}',
78
+ },
51
79
  },
52
80
  };
53
-
54
81
  this.test = {
55
82
  request: {
83
+ // Usamos o apiAddress direto para o teste não quebrar se o apiHost estiver vazio
56
84
  baseURL: '={{$credentials.apiAddress}}',
57
85
  url: '/auth',
58
86
  method: 'POST',
59
87
  body: {
60
- consumer_key: process.env.TRAY_CONSUMER_KEY,
88
+ consumer_key: process.env.TRAY_CONSUMER_KEY,
61
89
  consumer_secret: process.env.TRAY_CONSUMER_SECRET,
62
- code: '={{$credentials.authCode}}',
90
+ code: '={{$credentials.authCode}}',
63
91
  },
64
92
  },
65
93
  };
66
94
  }
67
-
68
95
  async preAuthentication(credentials) {
69
96
  const now = new Date();
70
97
  const tokenExpiration = credentials.tokenExpiration
71
98
  ? new Date(credentials.tokenExpiration)
72
99
  : null;
73
-
74
100
  if (credentials.accessToken && tokenExpiration && tokenExpiration > now) {
75
- return credentials; // Token válido em cache
101
+ return credentials;
76
102
  }
77
103
  if (credentials.refreshToken && tokenExpiration && tokenExpiration <= now) {
78
104
  return await this._refreshTokenFlow(credentials);
79
105
  }
80
106
  return await this._authenticateFlow(credentials);
81
107
  }
82
-
83
- _buildBaseUrl(credentials) {
84
- const addr = (credentials.apiAddress || '').toString().trim().replace(/\/$/, '');
85
- if (!addr) throw new Error('[Atendix] API Address não configurado nas credenciais');
86
- return addr;
87
- }
88
-
108
+ /**
109
+ * Renomeado para evitar conflito com a propriedade 'authenticate' da interface
110
+ */
89
111
  async _authenticateFlow(credentials) {
90
- const baseUrl = this._buildBaseUrl(credentials);
91
-
92
- // consumer_key e consumer_secret são do Atendix ficam em process.env no servidor N8N
93
- const consumerKey = process.env.TRAY_CONSUMER_KEY;
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;
94
116
  const consumerSecret = process.env.TRAY_CONSUMER_SECRET;
95
-
96
- if (!consumerKey || !consumerSecret) {
97
- throw new Error('[Atendix] Variáveis TRAY_CONSUMER_KEY / TRAY_CONSUMER_SECRET não configuradas no servidor N8N. Contate o suporte Atendix.');
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
+ };
98
143
  }
99
-
100
- console.log(`[Atendix v1.3.1] Autenticando em: ${baseUrl}/auth`);
101
-
102
- const response = await fetch(`${baseUrl}/auth`, {
103
- method: 'POST',
104
- headers: { 'Content-Type': 'application/json' },
105
- body: JSON.stringify({
106
- consumer_key: consumerKey,
107
- consumer_secret: consumerSecret,
108
- code: credentials.authCode,
109
- }),
110
- });
111
-
112
- const data = await response.json();
113
- if (!response.ok) {
114
- console.error('[Atendix] Erro na autenticação Tray:', data);
115
- throw new Error(data.message || `Erro HTTP ${response.status} na autenticação Tray`);
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}`);
116
147
  }
117
-
118
- console.log('[Atendix v1.3.1] Autenticação OK ✓');
119
- return {
120
- ...credentials,
121
- accessToken: data.access_token,
122
- refreshToken: data.refresh_token,
123
- apiHost: data.api_host,
124
- tokenExpiration: data.date_expiration_access_token,
125
- };
126
148
  }
127
-
128
149
  async _refreshTokenFlow(credentials) {
129
- const baseUrl = this._buildBaseUrl(credentials);
150
+ const apiAddress = credentials.apiAddress;
151
+ const refreshToken = credentials.refreshToken;
130
152
  try {
131
- const response = await fetch(`${baseUrl}/auth?refresh_token=${credentials.refreshToken}`);
132
- const data = await response.json();
133
- if (!response.ok) return await this._authenticateFlow(credentials);
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
+ }
134
158
  return {
135
159
  ...credentials,
136
- accessToken: data.access_token,
137
- refreshToken: data.refresh_token,
160
+ accessToken: data.access_token,
161
+ refreshToken: data.refresh_token,
138
162
  tokenExpiration: data.date_expiration_access_token,
139
163
  };
140
- } catch {
164
+ }
165
+ catch (error) {
141
166
  return await this._authenticateFlow(credentials);
142
167
  }
143
168
  }
@@ -1,379 +1,335 @@
1
1
  "use strict";
2
2
  /**
3
- * n8n-nodes-atendix — Node Atendix Tray Commerce
4
- * v1.3.0 — Correções críticas de homologação:
5
- * - URL: {store_id}.commercesuite.com.br/web_api
6
- * - Endpoints NF: GET /orders/:id/invoices (não /invoices?order_id=)
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: { light: 'file:tray.svg', dark: 'file:tray.svg' },
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 nativa com a Tray Commerce — v1.3.0',
25
- defaults: { name: 'Atendix - Tray API' },
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: [{ name: 'trayApiAuto', required: true, displayName: 'Atendix - Tray API' }],
30
+ credentials: [
31
+ {
32
+ name: 'trayApiAuto',
33
+ required: true,
34
+ displayName: 'Atendix - Tray API',
35
+ },
36
+ ],
29
37
  properties: [
30
- // ===== RESOURCE =====
38
+ // ==================== RESOURCE SELECTOR ====================
31
39
  {
32
- displayName: 'Resource', name: 'resource', type: 'options',
40
+ displayName: 'Resource',
41
+ name: 'resource',
42
+ type: 'options',
33
43
  noDataExpression: true,
34
44
  options: [
35
- { name: 'Pedido', value: 'order' },
36
- { name: 'Nota Fiscal', value: 'invoice' },
37
- { name: 'Cliente', value: 'customer' },
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', name: 'operation', type: 'options',
53
+ displayName: 'Operation',
54
+ name: 'operation',
55
+ type: 'options',
47
56
  noDataExpression: true,
48
57
  displayOptions: { show: { resource: ['order'] } },
49
58
  options: [
50
- { name: 'Buscar Pedido', value: 'get', action: 'Buscar pedido por ID' },
51
- { name: 'Dados Completos', value: 'getComplete', action: 'Buscar dados completos' },
52
- { name: 'Listar Pedidos', value: 'list', action: 'Listar pedidos com filtros' },
53
- { name: 'Atualizar Status', value: 'updateStatus', action: 'Atualizar status do pedido' },
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', name: 'orderId', type: 'string', required: true,
59
- displayOptions: { show: { resource: ['order'], operation: ['get', 'getComplete', 'updateStatus'] } },
60
- default: '', placeholder: '9847',
61
- },
62
- {
63
- displayName: 'Novo Status', name: 'orderStatus', type: 'options',
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', name: 'filters', type: 'collection',
76
- placeholder: 'Adicionar Filtro', default: {},
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', name: 'status', type: 'options', default: '',
91
+ displayName: 'Status',
92
+ name: 'status',
93
+ type: 'options',
81
94
  options: [
82
- { name: 'Pendente', value: 'pending' },
83
- { name: 'Aprovado', value: 'approved' },
84
- { name: 'Enviado', value: 'shipped' },
85
- { name: 'Entregue', value: 'delivered' },
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: 'ID da Nota Fiscal', name: 'invoiceId', type: 'string', required: true,
117
- displayOptions: { show: { resource: ['invoice'], operation: ['get'] } },
118
- default: '', placeholder: '1347',
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 por ID', value: 'get', action: 'Buscar cliente por ID' },
133
- { name: 'Listar Clientes', value: 'list', action: 'Listar clientes paginado' },
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: 'ID do Cliente', name: 'customerId', type: 'string', required: true,
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', value: 'email' },
148
- { name: 'Telefone', value: 'phone' },
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 de Busca', name: 'searchValue', type: 'string',
155
- displayOptions: { show: { resource: ['customer'], operation: ['search'] } },
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: 'Filtros', name: 'filters', type: 'collection',
160
- placeholder: 'Adicionar Filtro', default: {},
161
- displayOptions: { show: { resource: ['customer'], operation: ['list'] } },
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', value: 'get', action: 'Buscar produto por ID ou SKU' },
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', name: 'searchType', type: 'options',
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', value: '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', name: 'searchValue', type: 'string',
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
- const items = this.getInputData();
172
+ var _a, _b;
173
+ const items = this.getInputData();
216
174
  const returnData = [];
217
- const resource = this.getNodeParameter('resource', 0);
175
+ const resource = this.getNodeParameter('resource', 0);
218
176
  const operation = this.getNodeParameter('operation', 0);
219
-
220
- // Credenciais (n8n chama preAuthentication automaticamente)
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
- // v1.3.1: URL vem do campo apiAddress preenchido pelo usuário
224
- // Ex: https://1225878.commercesuite.com.br/web_api
225
- const baseUrl = (credentials.apiAddress || '').toString().trim().replace(/\/$/, '');
226
- const accessToken = credentials.accessToken;
227
-
228
- if (!baseUrl) {
229
- throw new n8n_workflow_1.NodeOperationError(
230
- this.getNode(),
231
- 'API Address não configurado nas credenciais Atendix. Preencha com a URL da sua loja.',
232
- );
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
+ }
233
238
  }
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', url: `${baseUrl}/orders/${orderId}`, qs, json: true,
248
- });
249
- } else if (operation === 'getComplete') {
250
- const orderId = this.getNodeParameter('orderId', i);
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 orderId = this.getNodeParameter('orderId', i);
279
- // ✅ CORRETO: GET /orders/:order_id/invoices
280
- responseData = await this.helpers.httpRequest({
281
- method: 'GET', url: `${baseUrl}/orders/${orderId}/invoices`, qs, json: true,
282
- });
283
- } else if (operation === 'get') {
284
- const orderId = this.getNodeParameter('orderId', i);
285
- const invoiceId = this.getNodeParameter('invoiceId', i);
286
- // ✅ CORRETO: GET /orders/:order_id/invoices/:invoice_id
287
- responseData = await this.helpers.httpRequest({
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
+ // 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: 'POST', url: `${baseUrl}/invoices`, qs, body: invoiceData, json: true,
271
+ method: 'GET',
272
+ url: `${baseUrl}/orders`,
273
+ qs,
274
+ json: true,
294
275
  });
295
276
  }
296
277
  }
297
-
298
- // ===== CLIENTES =====
299
- else if (resource === 'customer') {
278
+ // ==================== CLIENTES ====================
279
+ if (resource === 'customer') {
300
280
  if (operation === 'get') {
301
- const customerId = this.getNodeParameter('customerId', i);
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
- qs[searchType] = searchValue;
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', url: `${baseUrl}/customers`, qs, json: true,
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
- // ===== RASTREAMENTO =====
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 = this.getNodeParameter('searchType', i);
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', url: `${baseUrl}/products/${searchValue}`, qs, json: true,
303
+ method: 'GET',
304
+ url: `${baseUrl}/products/${searchValue}`,
305
+ qs,
306
+ json: true,
351
307
  });
352
- } else {
308
+ }
309
+ else {
353
310
  qs.sku = searchValue;
354
311
  responseData = await this.helpers.httpRequest({
355
- method: 'GET', url: `${baseUrl}/products`, qs, json: true,
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
- } catch (error) {
368
- const msg = error instanceof Error ? error.message : String(error);
323
+ }
324
+ catch (error) {
325
+ const errorMessage = error instanceof Error ? error.message : String(error);
369
326
  if (this.continueOnFail()) {
370
- returnData.push({ json: { error: msg, resource, operation }, pairedItem: { item: i } });
327
+ returnData.push({ json: { error: errorMessage }, pairedItem: { item: i } });
371
328
  continue;
372
329
  }
373
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), msg, { itemIndex: i });
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-atendix",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "Conector Atendix para integração nativa com a Tray Commerce",
5
5
  "keywords": ["n8n-community-node-package"],
6
6
  "license": "MIT",