n8n-nodes-atendix 1.0.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/credentials/TrayApiAuto.credentials.js +71 -96
- package/dist/nodes/Atendix/Atendix.node.js +253 -209
- package/index.js +6 -2
- package/package.json +33 -37
- package/README.md +0 -68
- package/dist/credentials/TrayApiAuto.credentials.d.ts +0 -16
- package/dist/icons/tray.svg +0 -21
- package/dist/nodes/Atendix/Atendix.node.d.ts +0 -11
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
|
6
7
|
*/
|
|
7
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
9
|
exports.TrayApiAuto = void 0;
|
|
9
|
-
|
|
10
|
-
const CONSUMER_SECRET = process.env.TRAY_CONSUMER_SECRET;
|
|
10
|
+
|
|
11
11
|
class TrayApiAuto {
|
|
12
12
|
constructor() {
|
|
13
13
|
this.name = 'trayApiAuto';
|
|
@@ -22,147 +22,122 @@ class TrayApiAuto {
|
|
|
22
22
|
displayName: 'API Address',
|
|
23
23
|
name: 'apiAddress',
|
|
24
24
|
type: 'string',
|
|
25
|
-
default: '
|
|
25
|
+
default: '',
|
|
26
26
|
required: true,
|
|
27
|
-
|
|
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',
|
|
28
29
|
},
|
|
29
30
|
{
|
|
30
31
|
displayName: 'Authorization Code',
|
|
31
32
|
name: 'authCode',
|
|
32
33
|
type: 'string',
|
|
33
|
-
typeOptions: {
|
|
34
|
-
password: true,
|
|
35
|
-
},
|
|
34
|
+
typeOptions: { password: true },
|
|
36
35
|
default: '',
|
|
37
36
|
required: true,
|
|
38
37
|
placeholder: 'a9777f8cdfe4cf41b0a72c295adef1f9c43a093052c9e6dd98d1e629a09f13f3',
|
|
39
38
|
description: 'Código de autorização único da loja (fornecido pela Tray)',
|
|
40
39
|
},
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
},
|
|
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: '' },
|
|
71
45
|
];
|
|
72
|
-
|
|
46
|
+
|
|
73
47
|
this.authenticate = {
|
|
74
48
|
type: 'generic',
|
|
75
49
|
properties: {
|
|
76
|
-
qs: {
|
|
77
|
-
access_token: '={{$credentials.accessToken}}',
|
|
78
|
-
},
|
|
50
|
+
qs: { access_token: '={{$credentials.accessToken}}' },
|
|
79
51
|
},
|
|
80
52
|
};
|
|
53
|
+
|
|
81
54
|
this.test = {
|
|
82
55
|
request: {
|
|
83
|
-
// Usamos o apiAddress direto para o teste não quebrar se o apiHost estiver vazio
|
|
84
56
|
baseURL: '={{$credentials.apiAddress}}',
|
|
85
57
|
url: '/auth',
|
|
86
58
|
method: 'POST',
|
|
87
59
|
body: {
|
|
88
|
-
consumer_key:
|
|
60
|
+
consumer_key: process.env.TRAY_CONSUMER_KEY,
|
|
89
61
|
consumer_secret: process.env.TRAY_CONSUMER_SECRET,
|
|
90
|
-
code:
|
|
62
|
+
code: '={{$credentials.authCode}}',
|
|
91
63
|
},
|
|
92
64
|
},
|
|
93
65
|
};
|
|
94
66
|
}
|
|
67
|
+
|
|
95
68
|
async preAuthentication(credentials) {
|
|
96
69
|
const now = new Date();
|
|
97
70
|
const tokenExpiration = credentials.tokenExpiration
|
|
98
71
|
? new Date(credentials.tokenExpiration)
|
|
99
72
|
: null;
|
|
73
|
+
|
|
100
74
|
if (credentials.accessToken && tokenExpiration && tokenExpiration > now) {
|
|
101
|
-
return credentials;
|
|
75
|
+
return credentials; // Token válido em cache
|
|
102
76
|
}
|
|
103
77
|
if (credentials.refreshToken && tokenExpiration && tokenExpiration <= now) {
|
|
104
78
|
return await this._refreshTokenFlow(credentials);
|
|
105
79
|
}
|
|
106
80
|
return await this._authenticateFlow(credentials);
|
|
107
81
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
+
|
|
111
89
|
async _authenticateFlow(credentials) {
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
//
|
|
115
|
-
const consumerKey
|
|
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;
|
|
116
94
|
const consumerSecret = process.env.TRAY_CONSUMER_SECRET;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
};
|
|
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.');
|
|
143
98
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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`);
|
|
147
116
|
}
|
|
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
|
+
};
|
|
148
126
|
}
|
|
127
|
+
|
|
149
128
|
async _refreshTokenFlow(credentials) {
|
|
150
|
-
const
|
|
151
|
-
const refreshToken = credentials.refreshToken;
|
|
129
|
+
const baseUrl = this._buildBaseUrl(credentials);
|
|
152
130
|
try {
|
|
153
|
-
const response = await fetch(`${
|
|
154
|
-
const data =
|
|
155
|
-
if (!response.ok)
|
|
156
|
-
return await this._authenticateFlow(credentials);
|
|
157
|
-
}
|
|
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);
|
|
158
134
|
return {
|
|
159
135
|
...credentials,
|
|
160
|
-
accessToken:
|
|
161
|
-
refreshToken:
|
|
136
|
+
accessToken: data.access_token,
|
|
137
|
+
refreshToken: data.refresh_token,
|
|
162
138
|
tokenExpiration: data.date_expiration_access_token,
|
|
163
139
|
};
|
|
164
|
-
}
|
|
165
|
-
catch (error) {
|
|
140
|
+
} catch {
|
|
166
141
|
return await this._authenticateFlow(credentials);
|
|
167
142
|
}
|
|
168
143
|
}
|
|
@@ -1,335 +1,379 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
7
8
|
*/
|
|
8
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
10
|
exports.Atendix = void 0;
|
|
10
11
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
12
|
+
|
|
11
13
|
const wait = (ms) => new Promise(res => setTimeout(res, ms));
|
|
14
|
+
|
|
12
15
|
class Atendix {
|
|
13
16
|
constructor() {
|
|
14
17
|
this.description = {
|
|
15
18
|
displayName: 'Atendix - Tray API',
|
|
16
19
|
name: 'atendix',
|
|
17
|
-
icon: {
|
|
18
|
-
light: 'file:tray.svg',
|
|
19
|
-
dark: 'file:tray.svg',
|
|
20
|
-
},
|
|
20
|
+
icon: { light: 'file:tray.svg', dark: 'file:tray.svg' },
|
|
21
21
|
group: ['transform'],
|
|
22
22
|
version: 1,
|
|
23
23
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
|
24
|
-
description: 'Integração com a
|
|
25
|
-
defaults: {
|
|
26
|
-
name: 'Atendix - Tray API',
|
|
27
|
-
},
|
|
24
|
+
description: 'Integração nativa com a Tray Commerce — v1.3.0',
|
|
25
|
+
defaults: { name: 'Atendix - Tray API' },
|
|
28
26
|
inputs: ['main'],
|
|
29
27
|
outputs: ['main'],
|
|
30
|
-
credentials: [
|
|
31
|
-
{
|
|
32
|
-
name: 'trayApiAuto',
|
|
33
|
-
required: true,
|
|
34
|
-
displayName: 'Atendix - Tray API',
|
|
35
|
-
},
|
|
36
|
-
],
|
|
28
|
+
credentials: [{ name: 'trayApiAuto', required: true, displayName: 'Atendix - Tray API' }],
|
|
37
29
|
properties: [
|
|
38
|
-
//
|
|
30
|
+
// ===== RESOURCE =====
|
|
39
31
|
{
|
|
40
|
-
displayName: 'Resource',
|
|
41
|
-
name: 'resource',
|
|
42
|
-
type: 'options',
|
|
32
|
+
displayName: 'Resource', name: 'resource', type: 'options',
|
|
43
33
|
noDataExpression: true,
|
|
44
34
|
options: [
|
|
45
|
-
{ name: 'Pedido',
|
|
46
|
-
{ name: '
|
|
47
|
-
{ name: '
|
|
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' },
|
|
48
40
|
],
|
|
49
41
|
default: 'order',
|
|
50
42
|
},
|
|
51
|
-
|
|
43
|
+
|
|
44
|
+
// ===== PEDIDO — OPERATIONS =====
|
|
52
45
|
{
|
|
53
|
-
displayName: 'Operation',
|
|
54
|
-
name: 'operation',
|
|
55
|
-
type: 'options',
|
|
46
|
+
displayName: 'Operation', name: 'operation', type: 'options',
|
|
56
47
|
noDataExpression: true,
|
|
57
48
|
displayOptions: { show: { resource: ['order'] } },
|
|
58
49
|
options: [
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
},
|
|
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' },
|
|
71
54
|
],
|
|
72
55
|
default: 'get',
|
|
73
56
|
},
|
|
74
57
|
{
|
|
75
|
-
displayName: 'ID do Pedido',
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
required: true,
|
|
79
|
-
displayOptions: { show: { resource: ['order'], operation: ['get'] } },
|
|
80
|
-
default: '',
|
|
58
|
+
displayName: 'ID do Pedido', name: 'orderId', type: 'string', required: true,
|
|
59
|
+
displayOptions: { show: { resource: ['order'], operation: ['get', 'getComplete', 'updateStatus'] } },
|
|
60
|
+
default: '', placeholder: '9847',
|
|
81
61
|
},
|
|
82
62
|
{
|
|
83
|
-
displayName: '
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
displayName: 'Filtros', name: 'filters', type: 'collection',
|
|
76
|
+
placeholder: 'Adicionar Filtro', default: {},
|
|
88
77
|
displayOptions: { show: { resource: ['order'], operation: ['list'] } },
|
|
89
78
|
options: [
|
|
90
79
|
{
|
|
91
|
-
displayName: 'Status',
|
|
92
|
-
name: 'status',
|
|
93
|
-
type: 'options',
|
|
80
|
+
displayName: 'Status', name: 'status', type: 'options', default: '',
|
|
94
81
|
options: [
|
|
95
|
-
{ name: 'Pendente',
|
|
96
|
-
{ name: 'Aprovado',
|
|
97
|
-
{ name: 'Enviado',
|
|
98
|
-
{ name: 'Entregue',
|
|
82
|
+
{ name: 'Pendente', value: 'pending' },
|
|
83
|
+
{ name: 'Aprovado', value: 'approved' },
|
|
84
|
+
{ name: 'Enviado', value: 'shipped' },
|
|
85
|
+
{ name: 'Entregue', value: 'delivered' },
|
|
99
86
|
{ name: 'Cancelado', value: 'cancelled' },
|
|
100
87
|
],
|
|
101
|
-
default: '',
|
|
102
88
|
},
|
|
103
89
|
{ 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: '' },
|
|
104
93
|
],
|
|
105
94
|
},
|
|
106
|
-
|
|
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
|
+
},
|
|
107
115
|
{
|
|
108
|
-
displayName: '
|
|
109
|
-
|
|
110
|
-
|
|
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',
|
|
111
129
|
noDataExpression: true,
|
|
112
130
|
displayOptions: { show: { resource: ['customer'] } },
|
|
113
131
|
options: [
|
|
114
|
-
{ name: 'Buscar
|
|
115
|
-
{ name: '
|
|
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' },
|
|
116
135
|
],
|
|
117
136
|
default: 'get',
|
|
118
137
|
},
|
|
119
138
|
{
|
|
120
|
-
displayName: '
|
|
121
|
-
name: 'searchType',
|
|
122
|
-
type: 'options',
|
|
139
|
+
displayName: 'ID do Cliente', name: 'customerId', type: 'string', required: true,
|
|
123
140
|
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'] } },
|
|
124
146
|
options: [
|
|
125
|
-
{ name: 'E-mail',
|
|
126
|
-
{ name: '
|
|
147
|
+
{ name: 'E-mail', value: 'email' },
|
|
148
|
+
{ name: 'Telefone', value: 'phone' },
|
|
149
|
+
{ name: 'CPF/CNPJ', value: 'cpf_cnpj'},
|
|
127
150
|
],
|
|
128
151
|
default: 'email',
|
|
129
152
|
},
|
|
130
153
|
{
|
|
131
|
-
displayName: 'Valor',
|
|
132
|
-
|
|
133
|
-
type: 'string',
|
|
134
|
-
displayOptions: { show: { resource: ['customer'], operation: ['get'] } },
|
|
154
|
+
displayName: 'Valor de Busca', name: 'searchValue', type: 'string',
|
|
155
|
+
displayOptions: { show: { resource: ['customer'], operation: ['search'] } },
|
|
135
156
|
default: '',
|
|
136
157
|
},
|
|
137
|
-
// ==================== PRODUTOS - OPERATIONS ====================
|
|
138
158
|
{
|
|
139
|
-
displayName: '
|
|
140
|
-
|
|
141
|
-
|
|
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',
|
|
142
188
|
noDataExpression: true,
|
|
143
189
|
displayOptions: { show: { resource: ['product'] } },
|
|
144
190
|
options: [
|
|
145
|
-
{ name: 'Buscar Produto',
|
|
146
|
-
{ name: 'Atualizar Estoque', value: 'updateStock', action: 'Atualizar estoque
|
|
191
|
+
{ name: 'Buscar Produto', value: 'get', action: 'Buscar produto por ID ou SKU' },
|
|
192
|
+
{ name: 'Atualizar Estoque', value: 'updateStock', action: 'Atualizar estoque' },
|
|
147
193
|
],
|
|
148
194
|
default: 'get',
|
|
149
195
|
},
|
|
150
196
|
{
|
|
151
|
-
displayName: 'Buscar Por',
|
|
152
|
-
name: 'searchType',
|
|
153
|
-
type: 'options',
|
|
197
|
+
displayName: 'Buscar Por', name: 'searchType', type: 'options',
|
|
154
198
|
displayOptions: { show: { resource: ['product'], operation: ['get'] } },
|
|
155
199
|
options: [
|
|
156
|
-
{ name: 'ID do Produto', value: 'id'
|
|
157
|
-
{ name: 'SKU',
|
|
200
|
+
{ name: 'ID do Produto', value: 'id' },
|
|
201
|
+
{ name: 'SKU', value: 'sku' },
|
|
158
202
|
],
|
|
159
203
|
default: 'id',
|
|
160
204
|
},
|
|
161
205
|
{
|
|
162
|
-
displayName: 'Valor',
|
|
163
|
-
name: 'searchValue',
|
|
164
|
-
type: 'string',
|
|
206
|
+
displayName: 'Valor', name: 'searchValue', type: 'string',
|
|
165
207
|
displayOptions: { show: { resource: ['product'], operation: ['get'] } },
|
|
166
208
|
default: '',
|
|
167
209
|
},
|
|
168
210
|
],
|
|
169
211
|
};
|
|
170
212
|
}
|
|
213
|
+
|
|
171
214
|
async execute() {
|
|
172
|
-
|
|
173
|
-
const items = this.getInputData();
|
|
215
|
+
const items = this.getInputData();
|
|
174
216
|
const returnData = [];
|
|
175
|
-
const resource
|
|
217
|
+
const resource = this.getNodeParameter('resource', 0);
|
|
176
218
|
const operation = this.getNodeParameter('operation', 0);
|
|
177
|
-
|
|
178
|
-
|
|
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
|
|
219
|
+
|
|
220
|
+
// Credenciais (n8n chama preAuthentication automaticamente)
|
|
183
221
|
const credentials = await this.getCredentials('trayApiAuto');
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
}
|
|
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
|
+
);
|
|
238
233
|
}
|
|
234
|
+
|
|
239
235
|
for (let i = 0; i < items.length; i++) {
|
|
240
236
|
try {
|
|
237
|
+
if (i > 0) await wait(500); // Rate-limit seguro: ~120 req/min (limite Tray: 180)
|
|
238
|
+
|
|
241
239
|
let responseData = {};
|
|
242
240
|
const qs = { access_token: accessToken };
|
|
243
|
-
|
|
241
|
+
|
|
242
|
+
// ===== PEDIDOS =====
|
|
244
243
|
if (resource === 'order') {
|
|
245
244
|
if (operation === 'get') {
|
|
246
245
|
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
|
-
}
|
|
252
246
|
responseData = await this.helpers.httpRequest({
|
|
253
|
-
method: 'GET',
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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,
|
|
257
270
|
});
|
|
258
271
|
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ===== NOTA FISCAL =====
|
|
275
|
+
// v1.3.0 FIX CRÍTICO: endpoints corretos são /orders/:id/invoices
|
|
276
|
+
else if (resource === 'invoice') {
|
|
259
277
|
if (operation === 'list') {
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
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
|
|
270
287
|
responseData = await this.helpers.httpRequest({
|
|
271
|
-
method: 'GET',
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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));
|
|
292
|
+
responseData = await this.helpers.httpRequest({
|
|
293
|
+
method: 'POST', url: `${baseUrl}/invoices`, qs, body: invoiceData, json: true,
|
|
275
294
|
});
|
|
276
295
|
}
|
|
277
296
|
}
|
|
278
|
-
|
|
279
|
-
|
|
297
|
+
|
|
298
|
+
// ===== CLIENTES =====
|
|
299
|
+
else if (resource === 'customer') {
|
|
280
300
|
if (operation === 'get') {
|
|
281
|
-
const
|
|
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);
|
|
282
314
|
const searchValue = this.getNodeParameter('searchValue', i);
|
|
283
|
-
|
|
284
|
-
qs.email = searchValue;
|
|
285
|
-
else
|
|
286
|
-
qs.cpf_cnpj = searchValue;
|
|
315
|
+
qs[searchType] = searchValue;
|
|
287
316
|
responseData = await this.helpers.httpRequest({
|
|
288
|
-
method: 'GET',
|
|
289
|
-
url: `${baseUrl}/customers`,
|
|
290
|
-
qs,
|
|
291
|
-
json: true,
|
|
317
|
+
method: 'GET', url: `${baseUrl}/customers`, qs, json: true,
|
|
292
318
|
});
|
|
293
319
|
}
|
|
294
|
-
// Nota da Gerente: Se for adicionar UPSERT aqui, o código deve ser incluído abaixo
|
|
295
320
|
}
|
|
296
|
-
|
|
297
|
-
|
|
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') {
|
|
298
345
|
if (operation === 'get') {
|
|
299
|
-
const searchType
|
|
346
|
+
const searchType = this.getNodeParameter('searchType', i);
|
|
300
347
|
const searchValue = this.getNodeParameter('searchValue', i);
|
|
301
348
|
if (searchType === 'id') {
|
|
302
349
|
responseData = await this.helpers.httpRequest({
|
|
303
|
-
method: 'GET',
|
|
304
|
-
url: `${baseUrl}/products/${searchValue}`,
|
|
305
|
-
qs,
|
|
306
|
-
json: true,
|
|
350
|
+
method: 'GET', url: `${baseUrl}/products/${searchValue}`, qs, json: true,
|
|
307
351
|
});
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
352
|
+
} else {
|
|
310
353
|
qs.sku = searchValue;
|
|
311
354
|
responseData = await this.helpers.httpRequest({
|
|
312
|
-
method: 'GET',
|
|
313
|
-
url: `${baseUrl}/products`,
|
|
314
|
-
qs,
|
|
315
|
-
json: true,
|
|
355
|
+
method: 'GET', url: `${baseUrl}/products`, qs, json: true,
|
|
316
356
|
});
|
|
317
357
|
}
|
|
318
358
|
}
|
|
319
|
-
// Nota da Gerente: Se for adicionar UPDATE_STOCK aqui, o código deve ser incluído abaixo
|
|
320
359
|
}
|
|
321
|
-
|
|
360
|
+
|
|
361
|
+
const executionData = this.helpers.constructExecutionMetaData(
|
|
362
|
+
this.helpers.returnJsonArray(responseData),
|
|
363
|
+
{ itemData: { item: i } },
|
|
364
|
+
);
|
|
322
365
|
returnData.push(...executionData);
|
|
323
|
-
|
|
324
|
-
catch (error) {
|
|
325
|
-
const
|
|
366
|
+
|
|
367
|
+
} catch (error) {
|
|
368
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
326
369
|
if (this.continueOnFail()) {
|
|
327
|
-
returnData.push({ json: { error:
|
|
370
|
+
returnData.push({ json: { error: msg, resource, operation }, pairedItem: { item: i } });
|
|
328
371
|
continue;
|
|
329
372
|
}
|
|
330
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(),
|
|
373
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), msg, { itemIndex: i });
|
|
331
374
|
}
|
|
332
375
|
}
|
|
376
|
+
|
|
333
377
|
return [returnData];
|
|
334
378
|
}
|
|
335
379
|
}
|
package/index.js
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use strict";
|
|
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; } });
|
package/package.json
CHANGED
|
@@ -1,37 +1,33 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "n8n-nodes-atendix",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Conector Atendix para integração com Tray Commerce",
|
|
5
|
-
"keywords": ["n8n-community-node-package"],
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"homepage": "https://atendix.co",
|
|
8
|
-
"author": {
|
|
9
|
-
"name": "Atendix",
|
|
10
|
-
"email": "contato@atendix.co"
|
|
11
|
-
},
|
|
12
|
-
"main": "index.js",
|
|
13
|
-
"scripts": {
|
|
14
|
-
"build": "
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
],
|
|
21
|
-
"n8n": {
|
|
22
|
-
"n8nNodesApiVersion": 1,
|
|
23
|
-
"credentials": [
|
|
24
|
-
"dist/credentials/TrayApiAuto.credentials.js"
|
|
25
|
-
],
|
|
26
|
-
"nodes": [
|
|
27
|
-
"dist/nodes/Atendix/Atendix.node.js"
|
|
28
|
-
]
|
|
29
|
-
},
|
|
30
|
-
"dependencies": {
|
|
31
|
-
"n8n-workflow": "^1.0.0"
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
"@types/node": "^18.0.0",
|
|
35
|
-
"typescript": "^5.0.0"
|
|
36
|
-
}
|
|
37
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-atendix",
|
|
3
|
+
"version": "1.3.1",
|
|
4
|
+
"description": "Conector Atendix para integração nativa com a Tray Commerce",
|
|
5
|
+
"keywords": ["n8n-community-node-package"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://atendix.co",
|
|
8
|
+
"author": {
|
|
9
|
+
"name": "Atendix",
|
|
10
|
+
"email": "contato@atendix.co"
|
|
11
|
+
},
|
|
12
|
+
"main": "index.js",
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "echo 'v1.3.0 pre-compiled — no build needed'"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"icons",
|
|
19
|
+
"index.js"
|
|
20
|
+
],
|
|
21
|
+
"n8n": {
|
|
22
|
+
"n8nNodesApiVersion": 1,
|
|
23
|
+
"credentials": [
|
|
24
|
+
"dist/credentials/TrayApiAuto.credentials.js"
|
|
25
|
+
],
|
|
26
|
+
"nodes": [
|
|
27
|
+
"dist/nodes/Atendix/Atendix.node.js"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"n8n-workflow": "^1.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/README.md
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# Atendix - Conector Tray Commerce para n8n 🚀
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-

|
|
5
|
-

|
|
6
|
-
|
|
7
|
-
O **Atendix** é o conector definitivo para integrar sua loja **Tray Commerce** ao ecossistema de automação do **n8n**. Esqueça a complexidade de lidar com tokens manuais e exposição de chaves sensíveis.
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## ✨ Por que usar o Atendix?
|
|
12
|
-
|
|
13
|
-
- 🔒 **Segurança Total:** Suas chaves `Consumer Key` e `Consumer Secret` ficam protegidas e nunca são expostas no workflow.
|
|
14
|
-
- 🔄 **Autenticação Automática:** Gerenciamento inteligente de `Access Token` e `Refresh Token` sem interrupções.
|
|
15
|
-
- ⚖️ **Respeito ao Rate Limit:** Implementação nativa de *Throttling* (500ms) para garantir conformidade com o limite de 180 req/min da Tray.
|
|
16
|
-
- 💼 **Modelo SaaS:** Licenciamento centralizado para garantir suporte e atualizações contínuas.
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## 🛠️ Instalação
|
|
21
|
-
|
|
22
|
-
Para instalar este nó em sua instância n8n:
|
|
23
|
-
|
|
24
|
-
1. Acesse **Settings** (Configurações).
|
|
25
|
-
2. Vá em **Community Nodes** (Nós da Comunidade).
|
|
26
|
-
3. Clique em **Install a node**.
|
|
27
|
-
4. Digite o nome do pacote: `n8n-nodes-atendix`.
|
|
28
|
-
5. Aceite os termos e clique em **Install**.
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## 🔑 Configuração das Credenciais
|
|
33
|
-
|
|
34
|
-
Após a instalação, configure sua credencial Atendix com apenas dois campos:
|
|
35
|
-
|
|
36
|
-
1. **API Address:** A URL da sua loja (ex: `https://{SUA_LOJA}.commercesuite.com.br/web_api`).
|
|
37
|
-
2. **Authorization Code:** O código de autorização gerado no painel da Tray.
|
|
38
|
-
|
|
39
|
-
> **Nota:** Certifique-se de que sua loja possui uma licença ativa em [Atendix.co](https://atendix.co) para liberar o tráfego de dados.
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## 📦 Recursos Disponíveis (v1.0.1)
|
|
44
|
-
|
|
45
|
-
- ✅ **Pedidos:** Busca por ID e Listagem com filtros de status e data.
|
|
46
|
-
- ✅ **Clientes:** Busca por e-mail ou documento (CPF/CNPJ).
|
|
47
|
-
- ✅ **Produtos:** Consulta rápida por ID ou SKU.
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## ⚙️ Requisitos Técnicos
|
|
52
|
-
|
|
53
|
-
- **n8n version:** >= 1.0.0
|
|
54
|
-
- **Node.js:** >= 18.x
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
## 📄 Licença
|
|
59
|
-
|
|
60
|
-
Distribuído sob a licença MIT. Veja `LICENSE` para mais informações.
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
## 📞 Suporte e Contato
|
|
65
|
-
|
|
66
|
-
Dúvidas ou problemas com a ativação da sua licença?
|
|
67
|
-
- **Site:** [https://atendix.co](https://atendix.co)
|
|
68
|
-
- **E-mail:** contato@atendix.co
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { IAuthenticateGeneric, ICredentialType, INodeProperties, ICredentialDataDecryptedObject, ICredentialTestRequest, Icon } from 'n8n-workflow';
|
|
2
|
-
export declare class TrayApiAuto implements ICredentialType {
|
|
3
|
-
name: string;
|
|
4
|
-
displayName: string;
|
|
5
|
-
icon: Icon;
|
|
6
|
-
documentationUrl: string;
|
|
7
|
-
properties: INodeProperties[];
|
|
8
|
-
authenticate: IAuthenticateGeneric;
|
|
9
|
-
preAuthentication(credentials: ICredentialDataDecryptedObject): Promise<ICredentialDataDecryptedObject>;
|
|
10
|
-
/**
|
|
11
|
-
* Renomeado para evitar conflito com a propriedade 'authenticate' da interface
|
|
12
|
-
*/
|
|
13
|
-
private _authenticateFlow;
|
|
14
|
-
private _refreshTokenFlow;
|
|
15
|
-
test: ICredentialTestRequest;
|
|
16
|
-
}
|
package/dist/icons/tray.svg
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<svg
|
|
3
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
4
|
-
viewBox="0 0 24 24"
|
|
5
|
-
fill="none"
|
|
6
|
-
stroke="#7D7D87"
|
|
7
|
-
stroke-width="1.3"
|
|
8
|
-
stroke-linecap="round"
|
|
9
|
-
stroke-linejoin="round"
|
|
10
|
-
>
|
|
11
|
-
<path d="M3 21 L3 6 C3 4.343 4.343 3 6 3 H18 C19.657 3 22 4.343 22 6 V14 C22 15.657 19.657 17 18 17 H8.5 L3 21 Z" />
|
|
12
|
-
|
|
13
|
-
<path d="M6.8 14V9.5" />
|
|
14
|
-
<circle cx="6.8" cy="8" r="1.5" />
|
|
15
|
-
|
|
16
|
-
<path d="M10.6 14V11.5L12.5 9" />
|
|
17
|
-
<circle cx="13.5" cy="7.8" r="1.5" />
|
|
18
|
-
|
|
19
|
-
<path d="M14 14V12.5C14 11.8 14.5 11.5 15.2 11.5H17" />
|
|
20
|
-
<circle cx="18.5" cy="11.5" r="1.5" />
|
|
21
|
-
</svg>
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Arquivo: n8n-nodes-atendix/nodes/Atendix/Atendix.node.ts
|
|
3
|
-
*
|
|
4
|
-
* Node oficial Atendix - Tray Commerce
|
|
5
|
-
* Versão 1.2.0 - Licenciamento SaaS + Full Operations
|
|
6
|
-
*/
|
|
7
|
-
import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
8
|
-
export declare class Atendix implements INodeType {
|
|
9
|
-
description: INodeTypeDescription;
|
|
10
|
-
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
11
|
-
}
|