@telepapo/sdk 0.4.0 → 0.5.0
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/package.json +1 -1
- package/src/index.js +47 -5
- package/src/pedido.js +74 -4
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -18,7 +18,7 @@ const { Signal } = require('../signal/telepapo_signal.js');
|
|
|
18
18
|
export { ArmazenamentoArquivo, ArmazenamentoMemoria, verificarAssinatura };
|
|
19
19
|
export { cifrarAnexo, decifrarAnexo } from './anexos.js';
|
|
20
20
|
export { validarBotoes, validarLista, botaoEscolhido, limparTexto } from './botoes.js';
|
|
21
|
-
export { pedidoDe } from './pedido.js';
|
|
21
|
+
export { pedidoDe, pedidoAcaoDe, enderecoDe, FORMAS_PAGAMENTO } from './pedido.js';
|
|
22
22
|
|
|
23
23
|
export class ErroTelePapo extends Error {
|
|
24
24
|
constructor(status, corpo) {
|
|
@@ -59,7 +59,7 @@ export class TelePapo {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
async http(metodo, caminho, { corpo, token, basic } = {}) {
|
|
62
|
-
const headers = { 'User-Agent': 'telepapo-sdk/0.
|
|
62
|
+
const headers = { 'User-Agent': 'telepapo-sdk/0.5' };
|
|
63
63
|
if (corpo !== undefined) headers['Content-Type'] = 'application/json';
|
|
64
64
|
if (token) headers.Authorization = `Bearer ${token}`;
|
|
65
65
|
if (basic) headers.Authorization = 'Basic ' + Buffer.from(`${this.clientId}:${this.credencial}`).toString('base64');
|
|
@@ -218,7 +218,7 @@ class Conta {
|
|
|
218
218
|
const token = await this.token();
|
|
219
219
|
const r = await this.cliente.fetch(`${this.cliente.apiUrl}/v1/eu/catalogo/codigo/${encodeURIComponent(codigo)}/foto`, {
|
|
220
220
|
method: 'PUT',
|
|
221
|
-
headers: { Authorization: `Bearer ${token}`, 'Content-Type': mime, 'User-Agent': 'telepapo-sdk/0.
|
|
221
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': mime, 'User-Agent': 'telepapo-sdk/0.5' },
|
|
222
222
|
body: imagem,
|
|
223
223
|
});
|
|
224
224
|
if (!r.ok) {
|
|
@@ -227,6 +227,48 @@ class Conta {
|
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
// ------------------------------------------------------------ pedidos
|
|
231
|
+
// Permissão `pedidos.gerenciar` (docs/API.md, seção 6.7). O servidor guarda só
|
|
232
|
+
// o registro (de quem, quando, situação e andamento); itens, preços, entrega e
|
|
233
|
+
// pagamento vêm na própria mensagem, cifrados (`pedidoDe(mensagem.conteudo)`).
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Pedidos recebidos, do mais novo para o mais antigo.
|
|
237
|
+
* @param {{situacao?: 'ABERTO'|'CONCLUIDO'|'CANCELADO', cliente?: string, antes?: string, limite?: number}} [filtro]
|
|
238
|
+
* @returns {Promise<{pedidos: Array<{mensagemId: string, cliente: {id: string, nome: string|null, username: string|null, telefone: string}, situacao: string, criadoEm: string, concluidoEm: string|null, acoes: Array<{codigo: string, descricao: string, em: string}>}>, proximo: string|null}>}
|
|
239
|
+
*/
|
|
240
|
+
listarPedidos(filtro = {}) {
|
|
241
|
+
const q = new URLSearchParams(Object.entries(filtro).filter(([, v]) => v != null).map(([k, v]) => [k, String(v)]));
|
|
242
|
+
return this.api('GET', `/v1/eu/pedidos${q.size ? `?${q}` : ''}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
pedido(mensagemId) {
|
|
246
|
+
return this.api('GET', `/v1/eu/pedidos/${encodeURIComponent(mensagemId)}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Grava uma resposta no andamento do pedido **e** avisa o cliente na
|
|
251
|
+
* conversa (texto citando o pedido, com `pedidoAcao`). `CONCLUIDO` conclui.
|
|
252
|
+
* Sem `descricao`, vale a das respostas do pedido configuradas no perfil.
|
|
253
|
+
* @param {string} cliente id de quem fez o pedido (`mensagem.de`)
|
|
254
|
+
* @param {string} mensagemId id da mensagem do pedido (`mensagem.conteudo.id`)
|
|
255
|
+
*/
|
|
256
|
+
async atualizarPedido(cliente, mensagemId, codigo, { descricao } = {}) {
|
|
257
|
+
const p = await this.api('POST', `/v1/eu/pedidos/${encodeURIComponent(mensagemId)}/acoes`, { codigo, ...(descricao ? { descricao } : {}) });
|
|
258
|
+
const feita = p.acoes[p.acoes.length - 1];
|
|
259
|
+
await this.enviar(cliente, {
|
|
260
|
+
tipo: 'texto',
|
|
261
|
+
texto: `${codigo === 'CONCLUIDO' ? '✅' : '📦'} ${feita.descricao}`,
|
|
262
|
+
resposta: { id: mensagemId, autorId: cliente, trecho: '🛒 Pedido' },
|
|
263
|
+
pedidoAcao: { pedido: mensagemId, codigo, descricao: feita.descricao },
|
|
264
|
+
});
|
|
265
|
+
return p;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
concluirPedido(cliente, mensagemId) {
|
|
269
|
+
return this.atualizarPedido(cliente, mensagemId, 'CONCLUIDO');
|
|
270
|
+
}
|
|
271
|
+
|
|
230
272
|
// ------------------------------------------------------------ receber
|
|
231
273
|
|
|
232
274
|
/** Busca o que está pendente (alternativa/complemento ao webhook), decifra e confirma. */
|
|
@@ -405,7 +447,7 @@ class Conta {
|
|
|
405
447
|
const token = await this.token();
|
|
406
448
|
const r = await this.cliente.fetch(`${this.cliente.apiUrl}/v1/anexos/${id}/conteudo`, {
|
|
407
449
|
method: 'PUT',
|
|
408
|
-
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/octet-stream', 'User-Agent': 'telepapo-sdk/0.
|
|
450
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/octet-stream', 'User-Agent': 'telepapo-sdk/0.5' },
|
|
409
451
|
body: blob,
|
|
410
452
|
});
|
|
411
453
|
if (!r.ok) {
|
|
@@ -420,7 +462,7 @@ class Conta {
|
|
|
420
462
|
async baixarAnexo(anexo) {
|
|
421
463
|
const token = await this.token();
|
|
422
464
|
const r = await this.cliente.fetch(`${this.cliente.apiUrl}/v1/anexos/${encodeURIComponent(anexo.id)}`, {
|
|
423
|
-
headers: { Authorization: `Bearer ${token}`, 'User-Agent': 'telepapo-sdk/0.
|
|
465
|
+
headers: { Authorization: `Bearer ${token}`, 'User-Agent': 'telepapo-sdk/0.5' },
|
|
424
466
|
});
|
|
425
467
|
if (!r.ok) {
|
|
426
468
|
const texto = await r.text();
|
package/src/pedido.js
CHANGED
|
@@ -1,18 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pedido do carrinho (docs/MENSAGENS-RICAS.md §6 do TelePapo):
|
|
3
|
-
* com a conta comercial monta o carrinho com itens do catálogo
|
|
2
|
+
* Pedido do carrinho (docs/MENSAGENS-RICAS.md §6, §8, §9 e §10 do TelePapo):
|
|
3
|
+
* quem conversa com a conta comercial monta o carrinho com itens do catálogo,
|
|
4
|
+
* escolhe entrega e pagamento, e envia.
|
|
4
5
|
*
|
|
5
6
|
* O preço é o que o CLIENTE VIU, montado no aparelho dele: confira código,
|
|
6
7
|
* preço e disponibilidade no seu cadastro antes de confirmar o pedido.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
const inteiro = (v, min, max) => Number.isInteger(v) && v >= min && v <= max;
|
|
10
|
-
|
|
11
|
+
// Controles e direção do texto (disfarçam texto); tudo vira uma linha.
|
|
12
|
+
const CONTROLES = /[\u0000-\u001F\u007F--]/g;
|
|
13
|
+
const texto = (v, max) => {
|
|
14
|
+
if (typeof v !== 'string') return null;
|
|
15
|
+
const t = v.replace(CONTROLES, ' ').replace(/\s+/g, ' ').trim();
|
|
16
|
+
return t ? t.slice(0, max) : null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Formas de pagamento (§10): código → nome. */
|
|
20
|
+
export const FORMAS_PAGAMENTO = {
|
|
21
|
+
PIX: 'Pix',
|
|
22
|
+
DINHEIRO: 'Dinheiro',
|
|
23
|
+
CREDITO: 'Cartão de crédito',
|
|
24
|
+
DEBITO: 'Cartão de débito',
|
|
25
|
+
VALE_REFEICAO: 'Vale-refeição',
|
|
26
|
+
VALE_ALIMENTACAO: 'Vale-alimentação',
|
|
27
|
+
BOLETO: 'Boleto',
|
|
28
|
+
TRANSFERENCIA: 'Transferência bancária',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const UFS = new Set('AC AL AP AM BA CE DF ES GO MA MT MS MG PA PB PR PE PI RJ RN RS RO RR SC SP SE TO'.split(' '));
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Endereço (§8) de uma mensagem (`conteudo.endereco`) ou de um pedido
|
|
35
|
+
* (`pedido.entrega`). Inválido: null.
|
|
36
|
+
* @returns {null | {rotulo: string|null, logradouro: string, numero: string, complemento: string|null, bairro: string|null, cidade: string, uf: string, cep: string|null, referencia: string|null}}
|
|
37
|
+
*/
|
|
38
|
+
export function enderecoDe(e) {
|
|
39
|
+
if (!e || typeof e !== 'object') return null;
|
|
40
|
+
const logradouro = texto(e.logradouro, 120);
|
|
41
|
+
const numero = texto(e.numero, 10);
|
|
42
|
+
const cidade = texto(e.cidade, 60);
|
|
43
|
+
const uf = typeof e.uf === 'string' && UFS.has(e.uf) ? e.uf : null;
|
|
44
|
+
const cep = typeof e.cep === 'string' && /^\d{5}-\d{3}$/.test(e.cep) ? e.cep : null;
|
|
45
|
+
if (!logradouro || !numero || !cidade || !uf) return null;
|
|
46
|
+
return {
|
|
47
|
+
rotulo: texto(e.rotulo, 30),
|
|
48
|
+
logradouro, numero,
|
|
49
|
+
complemento: texto(e.complemento, 60),
|
|
50
|
+
bairro: texto(e.bairro, 60),
|
|
51
|
+
cidade, uf, cep,
|
|
52
|
+
referencia: texto(e.referencia, 120),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Forma de pagamento escolhida no pedido (§10), ou null. */
|
|
57
|
+
function pagamentoDe(p) {
|
|
58
|
+
if (!p || typeof p !== 'object' || typeof p.codigo !== 'string' || !/^[A-Z0-9_]{1,30}$/.test(p.codigo)) return null;
|
|
59
|
+
const descricao = FORMAS_PAGAMENTO[p.codigo] ?? texto(p.descricao, 40);
|
|
60
|
+
if (!descricao) return null;
|
|
61
|
+
const troco = p.codigo === 'DINHEIRO' && inteiro(p.trocoParaCentavos, 1, 100_000_000) ? p.trocoParaCentavos : null;
|
|
62
|
+
return { codigo: p.codigo, descricao, trocoParaCentavos: troco };
|
|
63
|
+
}
|
|
11
64
|
|
|
12
65
|
/**
|
|
13
66
|
* Lê o pedido de uma mensagem recebida (`mensagem.conteudo`). Itens
|
|
14
67
|
* malformados são descartados; sem nenhum item válido, devolve null.
|
|
15
|
-
* @returns {null | { loja: string|null, itens: Array<{produtoId: string|null, codigo: string|null, descricao: string, quantidade: number, precoCentavos: number, totalCentavos: number}>, totalCentavos: number, moeda: string, observacao: string|null }}
|
|
68
|
+
* @returns {null | { loja: string|null, itens: Array<{produtoId: string|null, codigo: string|null, descricao: string, quantidade: number, precoCentavos: number, totalCentavos: number}>, totalCentavos: number, moeda: string, observacao: string|null, entrega: ReturnType<typeof enderecoDe>, pagamento: null | {codigo: string, descricao: string, trocoParaCentavos: number|null} }}
|
|
16
69
|
*/
|
|
17
70
|
export function pedidoDe(conteudo) {
|
|
18
71
|
const p = conteudo?.pedido;
|
|
@@ -37,5 +90,22 @@ export function pedidoDe(conteudo) {
|
|
|
37
90
|
totalCentavos: itens.reduce((s, i) => s + i.totalCentavos, 0),
|
|
38
91
|
moeda: 'BRL',
|
|
39
92
|
observacao: texto(p.observacao, 500),
|
|
93
|
+
entrega: enderecoDe(p.entrega),
|
|
94
|
+
pagamento: pagamentoDe(p.pagamento),
|
|
40
95
|
};
|
|
41
96
|
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Andamento de pedido numa mensagem (§9): a resposta da loja ou o
|
|
100
|
+
* cancelamento do cliente. Confira quem mandou: `CANCELADO` só vale do cliente
|
|
101
|
+
* do pedido; os outros códigos, só da loja.
|
|
102
|
+
* @returns {null | {pedido: string, codigo: string, descricao: string}}
|
|
103
|
+
*/
|
|
104
|
+
export function pedidoAcaoDe(conteudo) {
|
|
105
|
+
const a = conteudo?.pedidoAcao;
|
|
106
|
+
if (!a || typeof a !== 'object') return null;
|
|
107
|
+
const pedido = typeof a.pedido === 'string' && /^[A-Za-z0-9_-]{8,64}$/.test(a.pedido) ? a.pedido : null;
|
|
108
|
+
const codigo = typeof a.codigo === 'string' && /^[A-Z0-9_]{1,30}$/.test(a.codigo) ? a.codigo : null;
|
|
109
|
+
const descricao = texto(a.descricao, 40);
|
|
110
|
+
return pedido && codigo && descricao ? { pedido, codigo, descricao } : null;
|
|
111
|
+
}
|