n8n-nodes-atendix 1.3.5 → 1.3.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.
@@ -0,0 +1,13 @@
1
+ import { IAuthenticateGeneric, ICredentialType, INodeProperties, ICredentialDataDecryptedObject, ICredentialTestRequest } from 'n8n-workflow';
2
+ export declare class TrayApiAuto implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ icon: any;
6
+ documentationUrl: string;
7
+ properties: INodeProperties[];
8
+ authenticate: IAuthenticateGeneric;
9
+ preAuthentication(credentials: ICredentialDataDecryptedObject): Promise<ICredentialDataDecryptedObject>;
10
+ private _authenticateFlow;
11
+ private _refreshTokenFlow;
12
+ test: ICredentialTestRequest;
13
+ }
@@ -1,9 +1,4 @@
1
1
  "use strict";
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).
6
- */
7
2
  Object.defineProperty(exports, "__esModule", { value: true });
8
3
  exports.TrayApiAuto = void 0;
9
4
  const CONSUMER_KEY = 'edb6c989395fa2d22be2505dbbaa202a818ca0fd0eb222a7389fdedb3782a467';
@@ -12,10 +7,7 @@ class TrayApiAuto {
12
7
  constructor() {
13
8
  this.name = 'trayApiAuto';
14
9
  this.displayName = 'Atendix - Tray API';
15
- this.icon = {
16
- light: 'file:tray.svg',
17
- dark: 'file:tray.svg',
18
- };
10
+ this.icon = 'file:tray.svg';
19
11
  this.documentationUrl = 'https://developers.tray.com.br/docs/';
20
12
  this.properties = [
21
13
  {
@@ -24,69 +16,39 @@ class TrayApiAuto {
24
16
  type: 'string',
25
17
  default: 'https://api.tray.com.br',
26
18
  required: true,
19
+ placeholder: 'https://1225878.commercesuite.com.br/web_api',
27
20
  description: 'Endereço base da API Tray (geralmente https://api.tray.com.br)',
28
21
  },
29
22
  {
30
23
  displayName: 'Authorization Code',
31
24
  name: 'authCode',
32
25
  type: 'string',
33
- typeOptions: {
34
- password: true,
35
- },
26
+ typeOptions: { password: true },
36
27
  default: '',
37
28
  required: true,
38
29
  placeholder: 'a9777f8cdfe4cf41b0a72c295adef1f9c43a093052c9e6dd98d1e629a09f13f3',
39
30
  description: 'Código de autorização único da loja (fornecido pela Tray)',
40
31
  },
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
- },
32
+ { displayName: 'Access Token', name: 'accessToken', type: 'hidden', default: '' },
33
+ { displayName: 'Refresh Token', name: 'refreshToken', type: 'hidden', default: '' },
34
+ { displayName: 'API Host', name: 'apiHost', type: 'hidden', default: '' },
35
+ { displayName: 'Token Expiration', name: 'tokenExpiration', type: 'hidden', default: '' },
36
+ { displayName: 'Store ID', name: 'storeId', type: 'hidden', default: '' },
71
37
  ];
72
- // O segredo para o n8n injetar o token em todas as chamadas
73
38
  this.authenticate = {
74
39
  type: 'generic',
75
40
  properties: {
76
- qs: {
77
- access_token: '={{$credentials.accessToken}}',
78
- },
41
+ qs: { access_token: '={{$credentials.accessToken}}' },
79
42
  },
80
43
  };
81
44
  this.test = {
82
45
  request: {
83
- // Usamos o apiAddress direto para o teste não quebrar se o apiHost estiver vazio
84
46
  baseURL: '={{$credentials.apiAddress}}',
85
47
  url: '/auth',
86
48
  method: 'POST',
87
49
  body: {
88
- consumer_key: 'edb6c989395fa2d22be2505dbbaa202a818ca0fd0eb222a7389fdedb3782a467',
89
- consumer_secret: '750dfb677368a6c070ef24de04e843eaa6085a05be298e98d5ad7139421ef7e6',
50
+ consumer_key: CONSUMER_KEY,
51
+ consumer_secret: CONSUMER_SECRET,
90
52
  code: '={{$credentials.authCode}}',
91
53
  },
92
54
  },
@@ -105,24 +67,18 @@ class TrayApiAuto {
105
67
  }
106
68
  return await this._authenticateFlow(credentials);
107
69
  }
108
- /**
109
- * Renomeado para evitar conflito com a propriedade 'authenticate' da interface
110
- */
111
70
  async _authenticateFlow(credentials) {
112
71
  const apiAddress = credentials.apiAddress.replace(/\/$/, '');
113
72
  const authCode = credentials.authCode;
114
- // Pega as chaves direto do processo para garantir que não estão vazias
115
- const consumerKey = 'edb6c989395fa2d22be2505dbbaa202a818ca0fd0eb222a7389fdedb3782a467';
116
- const consumerSecret = '750dfb677368a6c070ef24de04e843eaa6085a05be298e98d5ad7139421ef7e6';
117
73
  console.log(` Tentando autenticar na URL: ${apiAddress}/auth`);
118
- console.log(` Usando Key: ${consumerKey ? 'Preenchida' : 'VAZIA!'}`);
74
+ console.log(` Usando Key: ${CONSUMER_KEY ? 'Preenchida' : 'VAZIA!'}`);
119
75
  try {
120
76
  const response = await fetch(`${apiAddress}/auth`, {
121
77
  method: 'POST',
122
78
  headers: { 'Content-Type': 'application/json' },
123
79
  body: JSON.stringify({
124
- consumer_key: consumerKey,
125
- consumer_secret: consumerSecret,
80
+ consumer_key: CONSUMER_KEY,
81
+ consumer_secret: CONSUMER_SECRET,
126
82
  code: authCode,
127
83
  }),
128
84
  });
@@ -136,7 +92,7 @@ class TrayApiAuto {
136
92
  ...credentials,
137
93
  accessToken: data.access_token,
138
94
  refreshToken: data.refresh_token,
139
- apiHost: data.api_host, // Geralmente vem algo como "https://1225878.commercesuite.com.br"
95
+ apiHost: data.api_host,
140
96
  storeId: data.store_id,
141
97
  tokenExpiration: data.date_expiration_access_token,
142
98
  };
@@ -152,9 +108,8 @@ class TrayApiAuto {
152
108
  try {
153
109
  const response = await fetch(`${apiAddress}/auth?refresh_token=${refreshToken}`);
154
110
  const data = (await response.json());
155
- if (!response.ok) {
111
+ if (!response.ok)
156
112
  return await this._authenticateFlow(credentials);
157
- }
158
113
  return {
159
114
  ...credentials,
160
115
  accessToken: data.access_token,
@@ -162,7 +117,7 @@ class TrayApiAuto {
162
117
  tokenExpiration: data.date_expiration_access_token,
163
118
  };
164
119
  }
165
- catch (error) {
120
+ catch {
166
121
  return await this._authenticateFlow(credentials);
167
122
  }
168
123
  }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Arquivo: n8n-nodes-atendix/nodes/Atendix/Atendix.node.ts
3
+ * v1.3.6 — Código-fonte original completo com variáveis de ambiente internalizadas
4
+ */
5
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
6
+ export declare class Atendix implements INodeType {
7
+ description: INodeTypeDescription;
8
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
9
+ }
@@ -1,13 +1,14 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Atendix = void 0;
2
4
  /**
3
5
  * 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
6
+ * v1.3.6 — Código-fonte original completo com variáveis de ambiente internalizadas
7
7
  */
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.Atendix = void 0;
10
8
  const n8n_workflow_1 = require("n8n-workflow");
9
+ const ATENDIX_AUTH_TOKEN = 'Bearer eyJhbGciOiJIUzI1NiJ9.e30.lEvvOyjcYVhrGiBHYZvV_HsmFEsc8lLw0yiNZk8lHJk';
10
+ const TRAY_CONSUMER_KEY = 'edb6c989395fa2d22be2505dbbaa202a818ca0fd0eb222a7389fdedb3782a467';
11
+ const TRAY_CONSUMER_SECRET = '750dfb677368a6c070ef24de04e843eaa6085a05be298e98d5ad7139421ef7e6';
11
12
  const wait = (ms) => new Promise(res => setTimeout(res, ms));
12
13
  class Atendix {
13
14
  constructor() {
@@ -22,75 +23,51 @@ class Atendix {
22
23
  version: 1,
23
24
  subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
24
25
  description: 'Integração com a plataforma Tray Commerce',
25
- defaults: {
26
- name: 'Atendix - Tray API',
27
- },
26
+ defaults: { name: 'Atendix - Tray API' },
28
27
  inputs: ['main'],
29
28
  outputs: ['main'],
30
29
  credentials: [
31
- {
32
- name: 'trayApiAuto',
33
- required: true,
34
- displayName: 'Atendix - Tray API',
35
- },
30
+ { name: 'trayApiAuto', required: true, displayName: 'Atendix - Tray API' },
36
31
  ],
37
32
  properties: [
38
- // ==================== RESOURCE SELECTOR ====================
33
+ // ==================== RESOURCE ====================
39
34
  {
40
- displayName: 'Resource',
41
- name: 'resource',
42
- type: 'options',
35
+ displayName: 'Resource', name: 'resource', type: 'options',
43
36
  noDataExpression: true,
44
37
  options: [
45
38
  { name: 'Pedido', value: 'order' },
46
39
  { name: 'Cliente', value: 'customer' },
47
40
  { name: 'Produto', value: 'product' },
41
+ { name: 'Categoria', value: 'category' },
42
+ { name: 'Variação', value: 'variant' },
43
+ { name: 'Imagem de Produto', value: 'productImage' },
44
+ { name: 'Nota Fiscal', value: 'invoice' },
48
45
  ],
49
46
  default: 'order',
50
47
  },
51
- // ==================== PEDIDOS - OPERATIONS ====================
48
+ // ==================== PEDIDOS ====================
52
49
  {
53
- displayName: 'Operation',
54
- name: 'operation',
55
- type: 'options',
50
+ displayName: 'Operation', name: 'operation', type: 'options',
56
51
  noDataExpression: true,
57
52
  displayOptions: { show: { resource: ['order'] } },
58
53
  options: [
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
+ { name: 'Buscar Pedido', value: 'get', description: 'Busca um pedido específico por ID', action: 'Buscar um pedido' },
55
+ { name: 'Listar Pedidos', value: 'list', description: 'Lista pedidos com filtros opcionais', action: 'Listar pedidos' },
71
56
  ],
72
57
  default: 'get',
73
58
  },
74
59
  {
75
- displayName: 'ID do Pedido',
76
- name: 'orderId',
77
- type: 'string',
78
- required: true,
60
+ displayName: 'ID do Pedido', name: 'orderId', type: 'string', required: true,
79
61
  displayOptions: { show: { resource: ['order'], operation: ['get'] } },
80
62
  default: '',
81
63
  },
82
64
  {
83
- displayName: 'Filtros',
84
- name: 'filters',
85
- type: 'collection',
86
- placeholder: 'Adicionar Filtro',
87
- default: {},
65
+ displayName: 'Filtros', name: 'filters', type: 'collection',
66
+ placeholder: 'Adicionar Filtro', default: {},
88
67
  displayOptions: { show: { resource: ['order'], operation: ['list'] } },
89
68
  options: [
90
69
  {
91
- displayName: 'Status',
92
- name: 'status',
93
- type: 'options',
70
+ displayName: 'Status', name: 'status', type: 'options', default: '',
94
71
  options: [
95
72
  { name: 'Pendente', value: 'pending' },
96
73
  { name: 'Aprovado', value: 'approved' },
@@ -98,16 +75,13 @@ class Atendix {
98
75
  { name: 'Entregue', value: 'delivered' },
99
76
  { name: 'Cancelado', value: 'cancelled' },
100
77
  ],
101
- default: '',
102
78
  },
103
79
  { displayName: 'Limite', name: 'limit', type: 'number', default: 50 },
104
80
  ],
105
81
  },
106
- // ==================== CLIENTES - OPERATIONS ====================
82
+ // ==================== CLIENTES ====================
107
83
  {
108
- displayName: 'Operation',
109
- name: 'operation',
110
- type: 'options',
84
+ displayName: 'Operation', name: 'operation', type: 'options',
111
85
  noDataExpression: true,
112
86
  displayOptions: { show: { resource: ['customer'] } },
113
87
  options: [
@@ -117,9 +91,7 @@ class Atendix {
117
91
  default: 'get',
118
92
  },
119
93
  {
120
- displayName: 'Buscar Por',
121
- name: 'searchType',
122
- type: 'options',
94
+ displayName: 'Buscar Por', name: 'searchType', type: 'options',
123
95
  displayOptions: { show: { resource: ['customer'], operation: ['get'] } },
124
96
  options: [
125
97
  { name: 'E-mail', value: 'email' },
@@ -128,17 +100,13 @@ class Atendix {
128
100
  default: 'email',
129
101
  },
130
102
  {
131
- displayName: 'Valor',
132
- name: 'searchValue',
133
- type: 'string',
103
+ displayName: 'Valor', name: 'searchValue', type: 'string',
134
104
  displayOptions: { show: { resource: ['customer'], operation: ['get'] } },
135
105
  default: '',
136
106
  },
137
- // ==================== PRODUTOS - OPERATIONS ====================
107
+ // ==================== PRODUTOS ====================
138
108
  {
139
- displayName: 'Operation',
140
- name: 'operation',
141
- type: 'options',
109
+ displayName: 'Operation', name: 'operation', type: 'options',
142
110
  noDataExpression: true,
143
111
  displayOptions: { show: { resource: ['product'] } },
144
112
  options: [
@@ -148,9 +116,7 @@ class Atendix {
148
116
  default: 'get',
149
117
  },
150
118
  {
151
- displayName: 'Buscar Por',
152
- name: 'searchType',
153
- type: 'options',
119
+ displayName: 'Buscar Por', name: 'searchType', type: 'options',
154
120
  displayOptions: { show: { resource: ['product'], operation: ['get'] } },
155
121
  options: [
156
122
  { name: 'ID do Produto', value: 'id' },
@@ -159,42 +125,157 @@ class Atendix {
159
125
  default: 'id',
160
126
  },
161
127
  {
162
- displayName: 'Valor',
163
- name: 'searchValue',
164
- type: 'string',
128
+ displayName: 'Valor', name: 'searchValue', type: 'string',
165
129
  displayOptions: { show: { resource: ['product'], operation: ['get'] } },
166
130
  default: '',
167
131
  },
132
+ // ==================== CATEGORIAS ====================
133
+ {
134
+ displayName: 'Operation', name: 'operation', type: 'options',
135
+ noDataExpression: true,
136
+ displayOptions: { show: { resource: ['category'] } },
137
+ options: [
138
+ { name: 'Listar Categorias', value: 'list', description: 'Lista todas as categorias da loja com paginação', action: 'Listar categorias' },
139
+ { name: 'Buscar Categoria', value: 'get', description: 'Busca uma categoria específica por ID', action: 'Buscar uma categoria' },
140
+ ],
141
+ default: 'list',
142
+ },
143
+ {
144
+ displayName: 'ID da Categoria', name: 'categoryId', type: 'string', required: true,
145
+ displayOptions: { show: { resource: ['category'], operation: ['get'] } },
146
+ default: '', description: 'Código da categoria na Tray',
147
+ },
148
+ {
149
+ displayName: 'Filtros', name: 'filters', type: 'collection',
150
+ placeholder: 'Adicionar Filtro', default: {},
151
+ displayOptions: { show: { resource: ['category'], operation: ['list'] } },
152
+ options: [
153
+ { displayName: 'Limite', name: 'limit', type: 'number', default: 50 },
154
+ { displayName: 'Página', name: 'page', type: 'number', default: 1 },
155
+ ],
156
+ },
157
+ // ==================== VARIAÇÕES ====================
158
+ {
159
+ displayName: 'Operation', name: 'operation', type: 'options',
160
+ noDataExpression: true,
161
+ displayOptions: { show: { resource: ['variant'] } },
162
+ options: [
163
+ { name: 'Listar Variações', value: 'list', description: 'Lista todas as variações de um produto', action: 'Listar variações' },
164
+ { name: 'Buscar Variação', value: 'get', description: 'Busca uma variação específica por ID', action: 'Buscar uma variação' },
165
+ ],
166
+ default: 'list',
167
+ },
168
+ {
169
+ displayName: 'ID do Produto', name: 'productIdForVariant', type: 'string', required: true,
170
+ displayOptions: { show: { resource: ['variant'], operation: ['list'] } },
171
+ default: '', description: 'ID do produto cujas variações serão listadas',
172
+ },
173
+ {
174
+ displayName: 'ID da Variação', name: 'variantId', type: 'string', required: true,
175
+ displayOptions: { show: { resource: ['variant'], operation: ['get'] } },
176
+ default: '', description: 'Código da variação na Tray',
177
+ },
178
+ {
179
+ displayName: 'Filtros', name: 'filters', type: 'collection',
180
+ placeholder: 'Adicionar Filtro', default: {},
181
+ displayOptions: { show: { resource: ['variant'], operation: ['list'] } },
182
+ options: [
183
+ { displayName: 'Limite', name: 'limit', type: 'number', default: 50 },
184
+ { displayName: 'Página', name: 'page', type: 'number', default: 1 },
185
+ ],
186
+ },
187
+ // ==================== IMAGENS DE PRODUTO ====================
188
+ {
189
+ displayName: 'Operation', name: 'operation', type: 'options',
190
+ noDataExpression: true,
191
+ displayOptions: { show: { resource: ['productImage'] } },
192
+ options: [
193
+ { name: 'Cadastrar Imagem de Produto', value: 'create', description: 'Cadastra ou atualiza a imagem de um produto', action: 'Cadastrar imagem de produto' },
194
+ { name: 'Cadastrar Imagem de Variação', value: 'createVariant', description: 'Cadastra ou atualiza a imagem de uma variação', action: 'Cadastrar imagem de variação' },
195
+ ],
196
+ default: 'create',
197
+ },
198
+ {
199
+ displayName: 'ID do Produto', name: 'productIdForImage', type: 'string', required: true,
200
+ displayOptions: { show: { resource: ['productImage'] } },
201
+ default: '', description: 'ID do produto na Tray',
202
+ },
203
+ {
204
+ displayName: 'URL da Imagem', name: 'imageUrl', type: 'string', required: true,
205
+ displayOptions: { show: { resource: ['productImage'] } },
206
+ default: '', description: 'URL pública da imagem a ser cadastrada',
207
+ },
208
+ {
209
+ displayName: 'ID da Variação', name: 'variantIdForImage', type: 'string', required: true,
210
+ displayOptions: { show: { resource: ['productImage'], operation: ['createVariant'] } },
211
+ default: '', description: 'ID da variação para associar a imagem',
212
+ },
213
+ // ==================== NOTA FISCAL ====================
214
+ {
215
+ displayName: 'Operation', name: 'operation', type: 'options',
216
+ noDataExpression: true,
217
+ displayOptions: { show: { resource: ['invoice'] } },
218
+ options: [
219
+ { name: 'Listar NFs do Pedido', value: 'list', description: 'Lista notas fiscais de um pedido específico', action: 'Listar notas fiscais do pedido' },
220
+ { name: 'Buscar Nota Fiscal', value: 'get', description: 'Busca uma nota fiscal por ID', action: 'Buscar nota fiscal por ID' },
221
+ { name: 'Buscar NF por Pedido', value: 'getByOrder', description: 'Busca a nota fiscal vinculada a um pedido', action: 'Buscar nota fiscal por pedido' },
222
+ { name: 'Cadastrar Nota Fiscal', value: 'create', description: 'Cadastra uma nova nota fiscal em um pedido', action: 'Cadastrar nota fiscal' },
223
+ ],
224
+ default: 'list',
225
+ },
226
+ {
227
+ displayName: 'ID do Pedido', name: 'invoiceOrderId', type: 'string', required: true,
228
+ displayOptions: { show: { resource: ['invoice'], operation: ['list', 'get', 'getByOrder', 'create'] } },
229
+ default: '', description: 'ID do pedido vinculado à nota fiscal',
230
+ },
231
+ {
232
+ displayName: 'ID da Nota Fiscal', name: 'invoiceId', type: 'string', required: true,
233
+ displayOptions: { show: { resource: ['invoice'], operation: ['get'] } },
234
+ default: '', description: 'ID da nota fiscal (ex: 1347). Requer também o ID do Pedido.',
235
+ },
236
+ // invoice list usa GET /orders/:id/invoices — sem filtros adicionais
237
+ {
238
+ displayName: 'Dados da Nota Fiscal', name: 'invoiceData', type: 'collection',
239
+ placeholder: 'Adicionar Campo', default: {},
240
+ displayOptions: { show: { resource: ['invoice'], operation: ['create'] } },
241
+ options: [
242
+ { displayName: 'Número da NF', name: 'number', type: 'string', default: '' },
243
+ { displayName: 'Série', name: 'serie', type: 'string', default: '' },
244
+ { displayName: 'Data de Emissão', name: 'issue_date', type: 'string', default: '' },
245
+ { displayName: 'Valor Total', name: 'value', type: 'number', default: 0 },
246
+ { displayName: 'Chave de Acesso', name: 'key', type: 'string', default: '' },
247
+ { displayName: 'XML da NF', name: 'xml', type: 'string', default: '' },
248
+ { displayName: 'Link (DANFE/PDF)', name: 'link', type: 'string', default: '' },
249
+ ],
250
+ },
168
251
  ],
169
252
  };
170
253
  }
171
254
  async execute() {
172
- var _a, _b;
255
+ var _a;
173
256
  const items = this.getInputData();
174
257
  const returnData = [];
175
258
  const resource = this.getNodeParameter('resource', 0);
176
259
  const operation = this.getNodeParameter('operation', 0);
177
- const atendixAuthToken = 'Bearer eyJhbGciOiJIUzI1NiJ9.e30.lEvvOyjcYVhrGiBHYZvV_HsmFEsc8lLw0yiNZk8lHJk';
178
- if (!atendixAuthToken) {
179
- console.error('❌ ATENDIX_AUTH_TOKEN não configurado nas variáveis de ambiente');
260
+ // Chave internalizadas não dependem de variáveis de ambiente
261
+ if (!ATENDIX_AUTH_TOKEN) {
180
262
  throw new Error('Configuração de validação Atendix incompleta. Entre em contato com o suporte.');
181
263
  }
182
- // 1. Get Credentials
264
+ // 1. Credenciais
183
265
  const credentials = await this.getCredentials('trayApiAuto');
184
266
  let baseUrl = (credentials.apiHost || credentials.apiAddress || '').trim().replace(/\/$/, '');
185
- let licenseCheck;
186
267
  // ==========================================
187
268
  // 1.1 VALIDAÇÃO DE LICENÇA (SaaS GATEKEEPER)
188
269
  // ==========================================
189
- // Pegamos a URL base da loja para validar no seu servidor
190
270
  const storeBaseUrl = baseUrl.replace('/web_api', '');
271
+ let licenseCheck;
191
272
  try {
192
273
  licenseCheck = await this.helpers.httpRequest({
193
274
  method: 'POST',
194
275
  url: 'https://n8n.mariapinho.com.br/webhook/valida-atendix',
195
276
  headers: {
196
277
  'Content-Type': 'application/json',
197
- 'Authorization': atendixAuthToken,
278
+ 'Authorization': ATENDIX_AUTH_TOKEN,
198
279
  },
199
280
  body: { url: storeBaseUrl },
200
281
  json: true,
@@ -202,19 +283,15 @@ class Atendix {
202
283
  console.error('LICENSE CHECK RESPONSE: ' + licenseCheck.autorizado);
203
284
  if (!licenseCheck || licenseCheck.autorizado !== true) {
204
285
  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!`);
286
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Sua licença Atendix para a loja ${storeBaseUrl} não está ativa. Acesse https://atendix.co`);
206
287
  }
207
288
  }
208
289
  catch (error) {
209
- console.error('ATENDIX_AUTH_TOKEN:', atendixAuthToken);
210
290
  console.error('STORE BASE URL:', storeBaseUrl);
211
- console.error('LICENSECHECK:', licenseCheck);
212
291
  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
292
  if (error instanceof n8n_workflow_1.NodeOperationError)
216
293
  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.");
294
+ 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
295
  }
219
296
  // 2. TOKEN MANAGER (Fallback)
220
297
  let accessToken = credentials.accessToken;
@@ -224,8 +301,8 @@ class Atendix {
224
301
  method: 'POST',
225
302
  url: `${baseUrl}/auth`,
226
303
  body: {
227
- consumer_key: 'edb6c989395fa2d22be2505dbbaa202a818ca0fd0eb222a7389fdedb3782a467',
228
- consumer_secret: '750dfb677368a6c070ef24de04e843eaa6085a05be298e98d5ad7139421ef7e6',
304
+ consumer_key: TRAY_CONSUMER_KEY,
305
+ consumer_secret: TRAY_CONSUMER_SECRET,
229
306
  code: credentials.authCode,
230
307
  },
231
308
  json: true,
@@ -236,25 +313,21 @@ class Atendix {
236
313
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Falha na Autenticação Tray: ${authError.message}`);
237
314
  }
238
315
  }
316
+ // ==========================================
317
+ // 3. LOOP PRINCIPAL — Rate limit: 500ms entre itens
318
+ // ==========================================
239
319
  for (let i = 0; i < items.length; i++) {
240
320
  try {
321
+ if (i > 0)
322
+ await wait(500);
241
323
  let responseData = {};
242
324
  const qs = { access_token: accessToken };
243
325
  // ==================== PEDIDOS ====================
244
326
  if (resource === 'order') {
245
327
  if (operation === 'get') {
246
328
  const orderId = this.getNodeParameter('orderId', i);
247
- if (i > 0) {
248
- // 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
- }
252
- responseData = await this.helpers.httpRequest({
253
- method: 'GET',
254
- url: `${baseUrl}/orders/${orderId}`,
255
- qs,
256
- json: true,
257
- });
329
+ console.log(`>>> ATENDIX - GET ${baseUrl}/orders/${orderId}`);
330
+ responseData = await this.helpers.httpRequest({ method: 'GET', url: `${baseUrl}/orders/${orderId}`, qs, json: true });
258
331
  }
259
332
  if (operation === 'list') {
260
333
  const filters = this.getNodeParameter('filters', i, {});
@@ -262,17 +335,7 @@ class Atendix {
262
335
  qs.status = filters.status;
263
336
  if (filters.limit)
264
337
  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
- }
270
- responseData = await this.helpers.httpRequest({
271
- method: 'GET',
272
- url: `${baseUrl}/orders`,
273
- qs,
274
- json: true,
275
- });
338
+ responseData = await this.helpers.httpRequest({ method: 'GET', url: `${baseUrl}/orders`, qs, json: true });
276
339
  }
277
340
  }
278
341
  // ==================== CLIENTES ====================
@@ -284,14 +347,8 @@ class Atendix {
284
347
  qs.email = searchValue;
285
348
  else
286
349
  qs.cpf_cnpj = searchValue;
287
- responseData = await this.helpers.httpRequest({
288
- method: 'GET',
289
- url: `${baseUrl}/customers`,
290
- qs,
291
- json: true,
292
- });
350
+ responseData = await this.helpers.httpRequest({ method: 'GET', url: `${baseUrl}/customers`, qs, json: true });
293
351
  }
294
- // Nota da Gerente: Se for adicionar UPSERT aqui, o código deve ser incluído abaixo
295
352
  }
296
353
  // ==================== PRODUTOS ====================
297
354
  if (resource === 'product') {
@@ -299,24 +356,83 @@ class Atendix {
299
356
  const searchType = this.getNodeParameter('searchType', i);
300
357
  const searchValue = this.getNodeParameter('searchValue', i);
301
358
  if (searchType === 'id') {
302
- responseData = await this.helpers.httpRequest({
303
- method: 'GET',
304
- url: `${baseUrl}/products/${searchValue}`,
305
- qs,
306
- json: true,
307
- });
359
+ responseData = await this.helpers.httpRequest({ method: 'GET', url: `${baseUrl}/products/${searchValue}`, qs, json: true });
308
360
  }
309
361
  else {
310
362
  qs.sku = searchValue;
311
- responseData = await this.helpers.httpRequest({
312
- method: 'GET',
313
- url: `${baseUrl}/products`,
314
- qs,
315
- json: true,
316
- });
363
+ responseData = await this.helpers.httpRequest({ method: 'GET', url: `${baseUrl}/products`, qs, json: true });
317
364
  }
318
365
  }
319
- // Nota da Gerente: Se for adicionar UPDATE_STOCK aqui, o código deve ser incluído abaixo
366
+ }
367
+ // ==================== CATEGORIAS ====================
368
+ if (resource === 'category') {
369
+ if (operation === 'list') {
370
+ const filters = this.getNodeParameter('filters', i, {});
371
+ if (filters.limit)
372
+ qs.limit = filters.limit;
373
+ if (filters.page)
374
+ qs.page = filters.page;
375
+ responseData = await this.helpers.httpRequest({ method: 'GET', url: `${baseUrl}/categories`, qs, json: true });
376
+ }
377
+ if (operation === 'get') {
378
+ const categoryId = this.getNodeParameter('categoryId', i);
379
+ responseData = await this.helpers.httpRequest({ method: 'GET', url: `${baseUrl}/categories/${categoryId}`, qs, json: true });
380
+ }
381
+ }
382
+ // ==================== VARIAÇÕES ====================
383
+ if (resource === 'variant') {
384
+ if (operation === 'list') {
385
+ const productIdForVariant = this.getNodeParameter('productIdForVariant', i);
386
+ const filters = this.getNodeParameter('filters', i, {});
387
+ qs.product_id = productIdForVariant;
388
+ if (filters.limit)
389
+ qs.limit = filters.limit;
390
+ if (filters.page)
391
+ qs.page = filters.page;
392
+ responseData = await this.helpers.httpRequest({ method: 'GET', url: `${baseUrl}/variants`, qs, json: true });
393
+ }
394
+ if (operation === 'get') {
395
+ const variantId = this.getNodeParameter('variantId', i);
396
+ responseData = await this.helpers.httpRequest({ method: 'GET', url: `${baseUrl}/variants/${variantId}`, qs, json: true });
397
+ }
398
+ }
399
+ // ==================== IMAGENS DE PRODUTO ====================
400
+ if (resource === 'productImage') {
401
+ const productIdForImage = this.getNodeParameter('productIdForImage', i);
402
+ const imageUrl = this.getNodeParameter('imageUrl', i);
403
+ if (operation === 'create') {
404
+ const body = { ProductImage: { product_id: productIdForImage, url: imageUrl } };
405
+ responseData = await this.helpers.httpRequest({ method: 'POST', url: `${baseUrl}/products/${productIdForImage}/images`, qs, body, json: true });
406
+ }
407
+ if (operation === 'createVariant') {
408
+ const variantIdForImage = this.getNodeParameter('variantIdForImage', i);
409
+ const body = { VariantImage: { product_id: productIdForImage, variant_id: variantIdForImage, url: imageUrl } };
410
+ responseData = await this.helpers.httpRequest({ method: 'POST', url: `${baseUrl}/variants/${variantIdForImage}/images`, qs, body, json: true });
411
+ }
412
+ }
413
+ // ==================== NOTA FISCAL ====================
414
+ if (resource === 'invoice') {
415
+ if (operation === 'list') {
416
+ // ✅ CORRETO: GET /orders/:order_id/invoices
417
+ // GET /invoices NÃO EXISTE na API Tray (confirmado no PRD §3.3)
418
+ const invoiceOrderId = this.getNodeParameter('invoiceOrderId', i);
419
+ responseData = await this.helpers.httpRequest({ method: 'GET', url: `${baseUrl}/orders/${invoiceOrderId}/invoices`, qs, json: true });
420
+ }
421
+ if (operation === 'get') {
422
+ const invoiceOrderId = this.getNodeParameter('invoiceOrderId', i);
423
+ const invoiceId = this.getNodeParameter('invoiceId', i);
424
+ responseData = await this.helpers.httpRequest({ method: 'GET', url: `${baseUrl}/orders/${invoiceOrderId}/invoices/${invoiceId}`, qs, json: true });
425
+ }
426
+ if (operation === 'getByOrder') {
427
+ const invoiceOrderId = this.getNodeParameter('invoiceOrderId', i);
428
+ responseData = await this.helpers.httpRequest({ method: 'GET', url: `${baseUrl}/orders/${invoiceOrderId}/invoices`, qs, json: true });
429
+ }
430
+ if (operation === 'create') {
431
+ const invoiceOrderId = this.getNodeParameter('invoiceOrderId', i);
432
+ const invoiceData = this.getNodeParameter('invoiceData', i, {});
433
+ const body = { Invoice: { order_id: invoiceOrderId, ...invoiceData } };
434
+ responseData = await this.helpers.httpRequest({ method: 'POST', url: `${baseUrl}/invoices`, qs, body, json: true });
435
+ }
320
436
  }
321
437
  const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(responseData), { itemData: { item: i } });
322
438
  returnData.push(...executionData);
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- var Atendix_node_1 = require("./dist/nodes/Atendix/Atendix.node");
4
- Object.defineProperty(exports, "Atendix", { enumerable: true, get: function () { return Atendix_node_1.Atendix; } });
5
- var TrayApiAuto_credentials_1 = require("./dist/credentials/TrayApiAuto.credentials");
6
- Object.defineProperty(exports, "TrayApiAuto", { enumerable: true, get: function () { return TrayApiAuto_credentials_1.TrayApiAuto; } });
3
+ var node = require("./dist/nodes/Atendix/Atendix.node");
4
+ Object.defineProperty(exports, "Atendix", { enumerable: true, get: function() { return node.Atendix; }});
5
+ var creds = require("./dist/credentials/TrayApiAuto.credentials");
6
+ Object.defineProperty(exports, "TrayApiAuto", { enumerable: true, get: function() { return creds.TrayApiAuto; }});
package/package.json CHANGED
@@ -1,8 +1,10 @@
1
1
  {
2
2
  "name": "n8n-nodes-atendix",
3
- "version": "1.3.5",
4
- "description": "Conector Atendix para integração nativa com a Tray Commerce",
5
- "keywords": ["n8n-community-node-package"],
3
+ "version": "1.3.7",
4
+ "description": "Conector Atendix para integração com Tray Commerce",
5
+ "keywords": [
6
+ "n8n-community-node-package"
7
+ ],
6
8
  "license": "MIT",
7
9
  "homepage": "https://atendix.co",
8
10
  "author": {
@@ -11,7 +13,7 @@
11
13
  },
12
14
  "main": "index.js",
13
15
  "scripts": {
14
- "build": "echo 'v1.3.0 pre-compiled — no build needed'"
16
+ "build": "tsc"
15
17
  },
16
18
  "files": [
17
19
  "dist",
@@ -27,7 +29,9 @@
27
29
  "dist/nodes/Atendix/Atendix.node.js"
28
30
  ]
29
31
  },
30
- "dependencies": {
31
- "n8n-workflow": "^1.0.0"
32
+ "devDependencies": {
33
+ "@types/node": "^18.19.130",
34
+ "n8n-workflow": "^2.13.0",
35
+ "typescript": "^5.9.3"
32
36
  }
33
37
  }