@radardata/sdk 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +202 -2
- package/dist/cjs/configuracao.js +28 -0
- package/dist/cjs/configuracao.js.map +1 -0
- package/dist/cjs/dados.js +44 -0
- package/dist/cjs/dados.js.map +1 -0
- package/dist/cjs/erros.js +86 -0
- package/dist/cjs/erros.js.map +1 -0
- package/dist/cjs/index.js +69 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/recursos/analises.js +53 -0
- package/dist/cjs/recursos/analises.js.map +1 -0
- package/dist/cjs/recursos/catalogo.js +21 -0
- package/dist/cjs/recursos/catalogo.js.map +1 -0
- package/dist/cjs/recursos/consultas.js +33 -0
- package/dist/cjs/recursos/consultas.js.map +1 -0
- package/dist/cjs/recursos/verify.js +39 -0
- package/dist/cjs/recursos/verify.js.map +1 -0
- package/dist/cjs/recursos/webhooks.js +45 -0
- package/dist/cjs/recursos/webhooks.js.map +1 -0
- package/dist/cjs/transporte.js +128 -0
- package/dist/cjs/transporte.js.map +1 -0
- package/dist/cjs/webhooks/assinatura.js +65 -0
- package/dist/cjs/webhooks/assinatura.js.map +1 -0
- package/dist/esm/configuracao.d.ts +27 -0
- package/dist/esm/configuracao.d.ts.map +1 -0
- package/dist/esm/configuracao.js +23 -0
- package/dist/esm/configuracao.js.map +1 -0
- package/dist/esm/dados.d.ts +59 -0
- package/dist/esm/dados.d.ts.map +1 -0
- package/dist/esm/dados.js +38 -0
- package/dist/esm/dados.js.map +1 -0
- package/dist/esm/erros.d.ts +53 -0
- package/dist/esm/erros.d.ts.map +1 -0
- package/dist/esm/erros.js +73 -0
- package/dist/esm/erros.js.map +1 -0
- package/dist/esm/index.d.ts +41 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +47 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/recursos/analises.d.ts +25 -0
- package/dist/esm/recursos/analises.d.ts.map +1 -0
- package/dist/esm/recursos/analises.js +49 -0
- package/dist/esm/recursos/analises.js.map +1 -0
- package/dist/esm/recursos/catalogo.d.ts +11 -0
- package/dist/esm/recursos/catalogo.d.ts.map +1 -0
- package/dist/esm/recursos/catalogo.js +17 -0
- package/dist/esm/recursos/catalogo.js.map +1 -0
- package/dist/esm/recursos/consultas.d.ts +22 -0
- package/dist/esm/recursos/consultas.d.ts.map +1 -0
- package/dist/esm/recursos/consultas.js +29 -0
- package/dist/esm/recursos/consultas.js.map +1 -0
- package/dist/esm/recursos/verify.d.ts +25 -0
- package/dist/esm/recursos/verify.d.ts.map +1 -0
- package/dist/esm/recursos/verify.js +35 -0
- package/dist/esm/recursos/verify.js.map +1 -0
- package/dist/esm/recursos/webhooks.d.ts +27 -0
- package/dist/esm/recursos/webhooks.d.ts.map +1 -0
- package/dist/esm/recursos/webhooks.js +41 -0
- package/dist/esm/recursos/webhooks.js.map +1 -0
- package/dist/esm/transporte.d.ts +35 -0
- package/dist/esm/transporte.d.ts.map +1 -0
- package/dist/esm/transporte.js +123 -0
- package/dist/esm/transporte.js.map +1 -0
- package/dist/esm/webhooks/assinatura.d.ts +32 -0
- package/dist/esm/webhooks/assinatura.d.ts.map +1 -0
- package/dist/esm/webhooks/assinatura.js +61 -0
- package/dist/esm/webhooks/assinatura.js.map +1 -0
- package/package.json +46 -4
- package/src/configuracao.ts +50 -0
- package/src/dados.ts +93 -0
- package/src/erros.ts +70 -0
- package/src/index.ts +66 -0
- package/src/recursos/analises.ts +61 -0
- package/src/recursos/catalogo.ts +17 -0
- package/src/recursos/consultas.ts +32 -0
- package/src/recursos/verify.ts +40 -0
- package/src/recursos/webhooks.ts +43 -0
- package/src/transporte.ts +145 -0
- package/src/webhooks/assinatura.ts +72 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Confere que o webhook veio mesmo do RadarData.
|
|
4
|
+
*
|
|
5
|
+
* Mandamos `X-RadarData-Signature: sha256=<hmac do CORPO CRU com o seu
|
|
6
|
+
* webhook_secret>`, mais `X-RadarData-Event`, `X-RadarData-Delivery` e
|
|
7
|
+
* `X-RadarData-Timestamp`.
|
|
8
|
+
*
|
|
9
|
+
* Aceita `string` ou `Uint8Array`, e nao `Buffer`, de proposito: `Buffer` e um
|
|
10
|
+
* global do Node e poria `@types/node` na superficie publica do pacote, o que
|
|
11
|
+
* quebra o `tsc` de quem compila para borda, Deno ou navegador. `Buffer` E um
|
|
12
|
+
* `Uint8Array`, entao o que o `express.raw()` entrega continua servindo.
|
|
13
|
+
*
|
|
14
|
+
* O CORPO TEM DE SER O CRU. Em Express e `express.raw()`, nao `express.json()`;
|
|
15
|
+
* reserializar o objeto ja decodificado muda espaco e ordem de chave, o HMAC
|
|
16
|
+
* muda junto e a conferencia falha sem motivo aparente. E o erro mais comum
|
|
17
|
+
* nesta parte, em qualquer integracao.
|
|
18
|
+
*
|
|
19
|
+
* LIMITE QUE VOCE PRECISA CONHECER: o timestamp e o id de entrega viajam em
|
|
20
|
+
* CABECALHO, fora do corpo assinado, entao a assinatura prova a ORIGEM e o
|
|
21
|
+
* CONTEUDO, e nao a hora. Ela nao protege contra reenvio de um aviso antigo e
|
|
22
|
+
* autentico por quem o tenha interceptado. A defesa pratica e guardar o
|
|
23
|
+
* `X-RadarData-Delivery` e ignorar o que ja foi processado, que voce deve
|
|
24
|
+
* fazer de qualquer forma, porque reenviamos em caso de falha.
|
|
25
|
+
*/
|
|
26
|
+
export function confereAssinatura(corpoCru, assinatura, segredo) {
|
|
27
|
+
if (!assinatura || !segredo)
|
|
28
|
+
return false;
|
|
29
|
+
const esperada = `sha256=${createHmac("sha256", segredo).update(corpoCru).digest("hex")}`;
|
|
30
|
+
const a = Buffer.from(esperada, "utf8");
|
|
31
|
+
const b = Buffer.from(assinatura, "utf8");
|
|
32
|
+
// `timingSafeEqual` exige tamanhos iguais e lança quando diferem; comparar
|
|
33
|
+
// o tamanho antes não vaza nada, porque o tamanho da assinatura é fixo e
|
|
34
|
+
// público.
|
|
35
|
+
if (a.length !== b.length)
|
|
36
|
+
return false;
|
|
37
|
+
// E não `===`: comparação normal vaza, pelo tempo que leva, quantos bytes
|
|
38
|
+
// iniciais bateram, e com isso dá para descobrir a assinatura byte a byte.
|
|
39
|
+
return timingSafeEqual(a, b);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Confere e devolve o evento decodificado, ou lança.
|
|
43
|
+
*
|
|
44
|
+
* @throws Error quando a assinatura nao confere
|
|
45
|
+
*/
|
|
46
|
+
export function exigirAssinatura(corpoCru, assinatura, segredo) {
|
|
47
|
+
if (!confereAssinatura(corpoCru, assinatura, segredo)) {
|
|
48
|
+
throw new Error("Assinatura do webhook do RadarData não confere.");
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
// `TextDecoder` e nao `Buffer.toString`: existe em Node, em navegador, em
|
|
52
|
+
// Deno e em Bun, e nao arrasta os tipos do Node para a superficie publica.
|
|
53
|
+
const texto = typeof corpoCru === "string" ? corpoCru : new TextDecoder().decode(corpoCru);
|
|
54
|
+
const dados = JSON.parse(texto);
|
|
55
|
+
return dados && typeof dados === "object" ? dados : {};
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=assinatura.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assinatura.js","sourceRoot":"","sources":["../../../src/webhooks/assinatura.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAA6B,EAC7B,UAAqC,EACrC,OAAe;IAEf,IAAI,CAAC,UAAU,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAE1C,MAAM,QAAQ,GAAG,UAAU,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IAE1F,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAE1C,2EAA2E;IAC3E,yEAAyE;IACzE,WAAW;IACX,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAExC,0EAA0E;IAC1E,2EAA2E;IAC3E,OAAO,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAA6B,EAC7B,UAAqC,EACrC,OAAe;IAEf,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACrE,CAAC;IAED,IAAI,CAAC;QACH,0EAA0E;QAC1E,2EAA2E;QAC3E,MAAM,KAAK,GAAG,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC3F,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAChC,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAE,KAAiC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@radardata/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SDK oficial do RadarData para Node e TypeScript: consultas, Verify, Motor de Analises e webhooks.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://radardata.com.br",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"radardata",
|
|
9
|
+
"kyc",
|
|
10
|
+
"verify",
|
|
11
|
+
"consultas",
|
|
12
|
+
"serpro",
|
|
13
|
+
"typescript"
|
|
14
|
+
],
|
|
15
|
+
"author": "RadarData",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=18"
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/cjs/index.js",
|
|
21
|
+
"module": "./dist/esm/index.js",
|
|
22
|
+
"types": "./dist/esm/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/esm/index.d.ts",
|
|
26
|
+
"import": "./dist/esm/index.js",
|
|
27
|
+
"require": "./dist/cjs/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"src",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"sideEffects": false,
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "rm -rf dist && tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && node -e \"require('fs').writeFileSync('dist/cjs/package.json', JSON.stringify({type:'commonjs'}))\"",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
41
|
+
"prepublishOnly": "npm run test && npm run build"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^22.7.0",
|
|
45
|
+
"typescript": "^5.6.0",
|
|
46
|
+
"vitest": "^2.1.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** O que o SDK precisa saber para falar com o RadarData. */
|
|
2
|
+
export interface Configuracao {
|
|
3
|
+
/** Token de API da empresa, criado no painel. Nunca o de sessao do painel. */
|
|
4
|
+
token: string;
|
|
5
|
+
/** Base da api. Trocavel para apontar o SDK a um ambiente seu. */
|
|
6
|
+
base?: string;
|
|
7
|
+
/** Milissegundos de espera por resposta. */
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Tentativas em chamada IDEMPOTENTE. Vale so onde o SDK manda
|
|
11
|
+
* `Idempotency-Key`; onde nao manda, repetir criaria cobranca nova e o SDK
|
|
12
|
+
* nao repete, qualquer que seja este numero.
|
|
13
|
+
*/
|
|
14
|
+
tentativas?: number;
|
|
15
|
+
/** Para teste: um `fetch` seu no lugar do global. */
|
|
16
|
+
fetch?: typeof globalThis.fetch;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const VERSAO = "0.1.0";
|
|
20
|
+
|
|
21
|
+
/** @internal */
|
|
22
|
+
export interface ConfigResolvida extends Required<Omit<Configuracao, "fetch">> {
|
|
23
|
+
fetch: typeof globalThis.fetch;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** @internal */
|
|
27
|
+
export function resolver(c: Configuracao): ConfigResolvida {
|
|
28
|
+
if (!c.token) throw new Error("RadarData: token de API é obrigatório.");
|
|
29
|
+
|
|
30
|
+
const buscar = c.fetch ?? globalThis.fetch;
|
|
31
|
+
if (typeof buscar !== "function") {
|
|
32
|
+
throw new Error(
|
|
33
|
+
"RadarData: `fetch` não existe neste runtime. Use Node 18 ou mais novo, ou passe um `fetch` na configuração.",
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
token: c.token,
|
|
39
|
+
base: (c.base ?? "https://api.radardata.com.br/api/v2").replace(/\/+$/, ""),
|
|
40
|
+
timeoutMs: c.timeoutMs ?? 30_000,
|
|
41
|
+
tentativas: c.tentativas ?? 3,
|
|
42
|
+
fetch: buscar,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @internal */
|
|
47
|
+
export function userAgent(): string {
|
|
48
|
+
const v = typeof process !== "undefined" ? process.versions?.node : undefined;
|
|
49
|
+
return `radardata-sdk-node/${VERSAO}${v ? ` node/${v}` : ""}`;
|
|
50
|
+
}
|
package/src/dados.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/** Tipos do que a api devolve, onde tipar compensa. */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Saldo da conta, em CENTAVOS.
|
|
5
|
+
*
|
|
6
|
+
* Centavo inteiro, nunca float: o sistema inteiro guarda dinheiro assim, e
|
|
7
|
+
* converter para ponto flutuante aqui reintroduziria o erro de
|
|
8
|
+
* arredondamento que o inteiro existe para evitar.
|
|
9
|
+
*/
|
|
10
|
+
export interface Saldo {
|
|
11
|
+
readonly centavos: number;
|
|
12
|
+
/** O que da para gastar agora: desconta o que ja esta reservado. */
|
|
13
|
+
readonly disponivelCentavos: number;
|
|
14
|
+
/** PRE (saldo) ou POS (fatura). */
|
|
15
|
+
readonly modoCobranca: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Um servico do catalogo, com o preco DAQUELA conta.
|
|
20
|
+
*
|
|
21
|
+
* O preco vem resolvido por empresa: duas contas podem ver precos diferentes
|
|
22
|
+
* para o mesmo slug, e por isso ele nao e constante no SDK.
|
|
23
|
+
*/
|
|
24
|
+
export interface Servico {
|
|
25
|
+
readonly slug: string;
|
|
26
|
+
readonly nome: string;
|
|
27
|
+
readonly categoria: string | null;
|
|
28
|
+
readonly descricao: string | null;
|
|
29
|
+
/** CPF, CNPJ, PLACA... o que a consulta espera receber. */
|
|
30
|
+
readonly tipoDocumento: string | null;
|
|
31
|
+
readonly precoCentavos: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Uma sessao de Verify.
|
|
36
|
+
*
|
|
37
|
+
* `url` so vem na CRIACAO: e o link que voce manda para a pessoa. Depois
|
|
38
|
+
* disso a consulta devolve estado e resultado, e nao o link de novo.
|
|
39
|
+
*/
|
|
40
|
+
export interface SessaoVerify {
|
|
41
|
+
readonly id: string;
|
|
42
|
+
/** pending, in_progress, completed, expired, canceled. */
|
|
43
|
+
readonly status: string;
|
|
44
|
+
readonly url: string | null;
|
|
45
|
+
/** APROVADO, REPROVADO, REVISAO_MANUAL; null enquanto nao concluiu. */
|
|
46
|
+
readonly resultado: string | null;
|
|
47
|
+
readonly expiraEm: string | null;
|
|
48
|
+
readonly referenceId: string | null;
|
|
49
|
+
/** Quanto este run custou para voce, em centavos. */
|
|
50
|
+
readonly custoCentavos: number;
|
|
51
|
+
/** O corpo cru, para o que o SDK nao tipou. */
|
|
52
|
+
readonly cru: Record<string, unknown>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** @internal */
|
|
56
|
+
export function paraSaldo(d: Record<string, unknown>): Saldo {
|
|
57
|
+
return {
|
|
58
|
+
centavos: Number(d.saldo ?? 0),
|
|
59
|
+
disponivelCentavos: Number(d.saldo_disponivel ?? 0),
|
|
60
|
+
modoCobranca: String(d.modo_cobranca ?? ""),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** @internal */
|
|
65
|
+
export function paraServico(d: Record<string, unknown>): Servico {
|
|
66
|
+
return {
|
|
67
|
+
slug: String(d.slug ?? ""),
|
|
68
|
+
nome: String(d.nome ?? ""),
|
|
69
|
+
categoria: (d.categoria as string | null) ?? null,
|
|
70
|
+
descricao: (d.descricao as string | null) ?? null,
|
|
71
|
+
tipoDocumento: (d.tipo_documento as string | null) ?? null,
|
|
72
|
+
precoCentavos: Number(d.preco ?? 0),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @internal */
|
|
77
|
+
export function paraSessao(d: Record<string, unknown>): SessaoVerify {
|
|
78
|
+
return {
|
|
79
|
+
id: String(d.session_id ?? ""),
|
|
80
|
+
status: String(d.status ?? ""),
|
|
81
|
+
url: (d.url as string | null) ?? null,
|
|
82
|
+
resultado: (d.outcome as string | null) ?? null,
|
|
83
|
+
expiraEm: (d.expires_at as string | null) ?? null,
|
|
84
|
+
referenceId: (d.reference_id as string | null) ?? null,
|
|
85
|
+
custoCentavos: Number(d.custo_total_centavos ?? 0),
|
|
86
|
+
cru: d,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Em reais, para exibir. O calculo continua em centavos. */
|
|
91
|
+
export function emReais(centavos: number): string {
|
|
92
|
+
return (centavos / 100).toLocaleString("pt-BR", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
93
|
+
}
|
package/src/erros.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Erros do SDK, um por situacao.
|
|
3
|
+
*
|
|
4
|
+
* A api devolve sempre `{"error": {"code": "...", "message": "..."}}`, e o
|
|
5
|
+
* `code` e o que vale para decidir no codigo: a `message` e escrita para
|
|
6
|
+
* pessoa e muda. Por isso ele fica acessivel aqui, e cada classe existe para
|
|
7
|
+
* um `catch` especifico, em vez de obrigar quem integra a comparar strings.
|
|
8
|
+
*/
|
|
9
|
+
export class ErroRadarData extends Error {
|
|
10
|
+
constructor(
|
|
11
|
+
mensagem: string,
|
|
12
|
+
readonly codigo: string | null = null,
|
|
13
|
+
readonly status: number | null = null,
|
|
14
|
+
/** O corpo cru, para quando o SDK nao previu algo. */
|
|
15
|
+
readonly corpo: Record<string, unknown> = {},
|
|
16
|
+
opcoes?: { cause?: unknown },
|
|
17
|
+
) {
|
|
18
|
+
super(mensagem, opcoes);
|
|
19
|
+
this.name = new.target.name;
|
|
20
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 401, token ausente, invalido ou revogado. */
|
|
25
|
+
export class NaoAutenticado extends ErroRadarData {}
|
|
26
|
+
|
|
27
|
+
/** 402, a conta nao tem saldo para esta operacao. */
|
|
28
|
+
export class SaldoInsuficiente extends ErroRadarData {}
|
|
29
|
+
|
|
30
|
+
/** 403, o token existe mas nao tem o escopo daquela rota. */
|
|
31
|
+
export class SemEscopo extends ErroRadarData {}
|
|
32
|
+
|
|
33
|
+
/** 404, recurso inexistente ou de outra empresa. */
|
|
34
|
+
export class NaoEncontrado extends ErroRadarData {}
|
|
35
|
+
|
|
36
|
+
/** 422, o que foi enviado nao passou na validacao. Veja `corpo`. */
|
|
37
|
+
export class EntradaInvalida extends ErroRadarData {}
|
|
38
|
+
|
|
39
|
+
/** 429, teto de chamadas atingido. */
|
|
40
|
+
export class MuitasTentativas extends ErroRadarData {}
|
|
41
|
+
|
|
42
|
+
/** 5xx, falha nossa. */
|
|
43
|
+
export class ErroDoServidor extends ErroRadarData {}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Nao houve resposta: timeout, DNS, conexao recusada.
|
|
47
|
+
*
|
|
48
|
+
* Separado dos erros com status de proposito: aqui o SDK NAO SABE se a
|
|
49
|
+
* operacao aconteceu. Numa chamada que gasta saldo isso importa, e o jeito de
|
|
50
|
+
* descobrir e consultar pela mesma chave de idempotencia, nao repetir as
|
|
51
|
+
* cegas.
|
|
52
|
+
*/
|
|
53
|
+
export class FalhaDeRede extends ErroRadarData {}
|
|
54
|
+
|
|
55
|
+
/** @internal */
|
|
56
|
+
export function erroDoStatus(
|
|
57
|
+
status: number,
|
|
58
|
+
mensagem: string,
|
|
59
|
+
codigo: string | null,
|
|
60
|
+
corpo: Record<string, unknown>,
|
|
61
|
+
): ErroRadarData {
|
|
62
|
+
if (status === 401) return new NaoAutenticado(mensagem, codigo, status, corpo);
|
|
63
|
+
if (status === 402) return new SaldoInsuficiente(mensagem, codigo, status, corpo);
|
|
64
|
+
if (status === 403) return new SemEscopo(mensagem, codigo, status, corpo);
|
|
65
|
+
if (status === 404) return new NaoEncontrado(mensagem, codigo, status, corpo);
|
|
66
|
+
if (status === 422) return new EntradaInvalida(mensagem, codigo, status, corpo);
|
|
67
|
+
if (status === 429) return new MuitasTentativas(mensagem, codigo, status, corpo);
|
|
68
|
+
if (status >= 500) return new ErroDoServidor(mensagem, codigo, status, corpo);
|
|
69
|
+
return new ErroRadarData(mensagem, codigo, status, corpo);
|
|
70
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type Configuracao, resolver } from "./configuracao.js";
|
|
2
|
+
import { Analises } from "./recursos/analises.js";
|
|
3
|
+
import { Catalogo } from "./recursos/catalogo.js";
|
|
4
|
+
import { Consultas } from "./recursos/consultas.js";
|
|
5
|
+
import { Verify } from "./recursos/verify.js";
|
|
6
|
+
import { Webhooks } from "./recursos/webhooks.js";
|
|
7
|
+
import { Transporte } from "./transporte.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Ponto de entrada do SDK.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { RadarData } from "@radardata/sdk";
|
|
14
|
+
*
|
|
15
|
+
* const radar = new RadarData({ token: process.env.RADARDATA_TOKEN! });
|
|
16
|
+
* const saldo = await radar.catalogo.saldo();
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* O token e o de API, criado no painel, e NAO o de sessao do painel. Ele
|
|
20
|
+
* carrega escopos: uma chamada fora do escopo do token volta `SemEscopo`, e
|
|
21
|
+
* isso e proposital, para um token de servidor nao poder redirecionar os seus
|
|
22
|
+
* webhooks.
|
|
23
|
+
*/
|
|
24
|
+
export class RadarData {
|
|
25
|
+
readonly catalogo: Catalogo;
|
|
26
|
+
|
|
27
|
+
readonly consultas: Consultas;
|
|
28
|
+
|
|
29
|
+
readonly verify: Verify;
|
|
30
|
+
|
|
31
|
+
readonly analises: Analises;
|
|
32
|
+
|
|
33
|
+
readonly webhooks: Webhooks;
|
|
34
|
+
|
|
35
|
+
constructor(config: Configuracao) {
|
|
36
|
+
const t = new Transporte(resolver(config));
|
|
37
|
+
|
|
38
|
+
this.catalogo = new Catalogo(t);
|
|
39
|
+
this.consultas = new Consultas(t);
|
|
40
|
+
this.verify = new Verify(t);
|
|
41
|
+
this.analises = new Analises(t);
|
|
42
|
+
this.webhooks = new Webhooks(t);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { chaveIdempotencia } from "./transporte.js";
|
|
47
|
+
export { confereAssinatura, exigirAssinatura } from "./webhooks/assinatura.js";
|
|
48
|
+
export { emReais } from "./dados.js";
|
|
49
|
+
export type { Configuracao } from "./configuracao.js";
|
|
50
|
+
export type { Saldo, Servico, SessaoVerify } from "./dados.js";
|
|
51
|
+
export {
|
|
52
|
+
EntradaInvalida,
|
|
53
|
+
ErroDoServidor,
|
|
54
|
+
ErroRadarData,
|
|
55
|
+
FalhaDeRede,
|
|
56
|
+
MuitasTentativas,
|
|
57
|
+
NaoAutenticado,
|
|
58
|
+
NaoEncontrado,
|
|
59
|
+
SaldoInsuficiente,
|
|
60
|
+
SemEscopo,
|
|
61
|
+
} from "./erros.js";
|
|
62
|
+
export { Analises } from "./recursos/analises.js";
|
|
63
|
+
export { Catalogo } from "./recursos/catalogo.js";
|
|
64
|
+
export { Consultas } from "./recursos/consultas.js";
|
|
65
|
+
export { Verify } from "./recursos/verify.js";
|
|
66
|
+
export { Webhooks } from "./recursos/webhooks.js";
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type Transporte, chaveIdempotencia } from "../transporte.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Motor de Analises. Escopo `analises`. GASTA SALDO.
|
|
5
|
+
*
|
|
6
|
+
* E ASSINCRONO: criar devolve o id e o resultado chega por webhook. Consultar
|
|
7
|
+
* em laco funciona, mas o webhook e o caminho previsto; veja
|
|
8
|
+
* `confereAssinatura` para conferir que o aviso e nosso.
|
|
9
|
+
*
|
|
10
|
+
* Um 404 aqui pode querer dizer "o Motor nao esta liberado para a sua conta",
|
|
11
|
+
* e nao "caminho errado": enquanto ele nao esta liberado, nada dele aparece
|
|
12
|
+
* para o cliente, de proposito.
|
|
13
|
+
*/
|
|
14
|
+
export class Analises {
|
|
15
|
+
constructor(private readonly t: Transporte) {}
|
|
16
|
+
|
|
17
|
+
/** Produtos liberados para esta conta. */
|
|
18
|
+
async produtos(): Promise<Record<string, unknown>[]> {
|
|
19
|
+
return this.t.pedir<Record<string, unknown>[]>("GET", "analises/produtos");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async criar(
|
|
23
|
+
slug: string,
|
|
24
|
+
campos: Record<string, unknown>,
|
|
25
|
+
opcoes: Record<string, unknown> = {},
|
|
26
|
+
chave?: string,
|
|
27
|
+
): Promise<Record<string, unknown>> {
|
|
28
|
+
return this.t.pedir("POST", `analises/${encodeURIComponent(slug)}`, {
|
|
29
|
+
corpo: { campos, ...opcoes },
|
|
30
|
+
chaveIdempotencia: chave ?? chaveIdempotencia(),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async mostrar(id: string): Promise<Record<string, unknown>> {
|
|
35
|
+
return this.t.pedir("GET", `analises/${encodeURIComponent(id)}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async decidir(id: string, decisao: string, motivo?: string): Promise<Record<string, unknown>> {
|
|
39
|
+
const corpo: Record<string, unknown> = { decisao };
|
|
40
|
+
if (motivo !== undefined) corpo.motivo = motivo;
|
|
41
|
+
return this.t.pedir("POST", `analises/${encodeURIComponent(id)}/decisao`, { corpo });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async monitorar(
|
|
45
|
+
slug: string,
|
|
46
|
+
campos: Record<string, unknown>,
|
|
47
|
+
opcoes: Record<string, unknown> = {},
|
|
48
|
+
): Promise<Record<string, unknown>> {
|
|
49
|
+
return this.t.pedir("POST", `analises/monitorar/${encodeURIComponent(slug)}`, {
|
|
50
|
+
corpo: { campos, ...opcoes },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async monitoramentos(): Promise<Record<string, unknown>[]> {
|
|
55
|
+
return this.t.pedir<Record<string, unknown>[]>("GET", "analises/monitoramentos");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async cancelarMonitoramento(id: string): Promise<Record<string, unknown>> {
|
|
59
|
+
return this.t.pedir("DELETE", `analises/monitoramentos/${encodeURIComponent(id)}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Saldo, type Servico, paraSaldo, paraServico } from "../dados.js";
|
|
2
|
+
import type { Transporte } from "../transporte.js";
|
|
3
|
+
|
|
4
|
+
/** Catalogo e saldo. Escopo `leitura`, nao gasta nada. */
|
|
5
|
+
export class Catalogo {
|
|
6
|
+
constructor(private readonly t: Transporte) {}
|
|
7
|
+
|
|
8
|
+
/** O que ESTA conta pode consultar, com o preco dela. */
|
|
9
|
+
async servicos(): Promise<Servico[]> {
|
|
10
|
+
const dados = await this.t.pedir<Record<string, unknown>[]>("GET", "servicos");
|
|
11
|
+
return dados.map(paraServico);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async saldo(): Promise<Saldo> {
|
|
15
|
+
return paraSaldo(await this.t.pedir("GET", "saldo"));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Transporte, chaveIdempotencia } from "../transporte.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Consultas. Escopo `consultas`. GASTA SALDO.
|
|
5
|
+
*
|
|
6
|
+
* O retorno vem como objeto solto e nao tipado de proposito: cada servico
|
|
7
|
+
* devolve um corpo diferente, a lista cresce, e tipar tudo aqui faria o SDK
|
|
8
|
+
* desatualizar a cada servico novo sem ninguem perceber.
|
|
9
|
+
*/
|
|
10
|
+
export class Consultas {
|
|
11
|
+
constructor(private readonly t: Transporte) {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Executa uma consulta pelo slug do servico.
|
|
15
|
+
*
|
|
16
|
+
* A chave de idempotencia e GERADA quando voce nao passa uma, e e ela que
|
|
17
|
+
* torna seguro repetir: a mesma chave devolve a mesma resposta em vez de
|
|
18
|
+
* cobrar de novo. Guarde a sua se quiser poder reconsultar o resultado
|
|
19
|
+
* depois de um timeout.
|
|
20
|
+
*/
|
|
21
|
+
async executar(
|
|
22
|
+
slug: string,
|
|
23
|
+
documento: string,
|
|
24
|
+
extras: Record<string, unknown> = {},
|
|
25
|
+
chave?: string,
|
|
26
|
+
): Promise<Record<string, unknown>> {
|
|
27
|
+
return this.t.pedir("POST", slug, {
|
|
28
|
+
corpo: { documento, ...extras },
|
|
29
|
+
chaveIdempotencia: chave ?? chaveIdempotencia(),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type SessaoVerify, paraSessao } from "../dados.js";
|
|
2
|
+
import type { Transporte } from "../transporte.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Verify: verificacao de identidade. Escopo `verify`. GASTA SALDO.
|
|
6
|
+
*
|
|
7
|
+
* ATENCAO ao criar: esta rota NAO tem idempotencia na api, entao o SDK nao
|
|
8
|
+
* repete a chamada sozinho. Se der timeout, NAO chame de novo as cegas: mande
|
|
9
|
+
* um `reference_id` seu na criacao e, na duvida, procure por ele antes de
|
|
10
|
+
* criar outra, senao voce paga duas sessoes.
|
|
11
|
+
*/
|
|
12
|
+
export class Verify {
|
|
13
|
+
constructor(private readonly t: Transporte) {}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Cria a sessao e devolve a URL para mandar a pessoa.
|
|
17
|
+
*
|
|
18
|
+
* @param fluxo uuid do fluxo ou o alias dele (ex.: "kyc-cnpj")
|
|
19
|
+
* @param input o que o fluxo pede de entrada (ex.: cpf)
|
|
20
|
+
* @param opcoes prefill, consent, redirect_url, webhook_url, reference_id
|
|
21
|
+
*/
|
|
22
|
+
async criarSessao(
|
|
23
|
+
fluxo: string,
|
|
24
|
+
input: Record<string, unknown> = {},
|
|
25
|
+
opcoes: Record<string, unknown> = {},
|
|
26
|
+
): Promise<SessaoVerify> {
|
|
27
|
+
const corpo: Record<string, unknown> = { flow_id: fluxo, ...opcoes };
|
|
28
|
+
if (Object.keys(input).length > 0) corpo.input = input;
|
|
29
|
+
|
|
30
|
+
return paraSessao(await this.t.pedir("POST", "verify/sessions", { corpo }));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async sessao(id: string): Promise<SessaoVerify> {
|
|
34
|
+
return paraSessao(await this.t.pedir("GET", `verify/sessions/${encodeURIComponent(id)}`));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async cancelar(id: string): Promise<SessaoVerify> {
|
|
38
|
+
return paraSessao(await this.t.pedir("POST", `verify/sessions/${encodeURIComponent(id)}/cancel`));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Transporte } from "../transporte.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Para onde mandamos os eventos, e o segredo que os assina.
|
|
5
|
+
*
|
|
6
|
+
* Escopo `webhooks`, que e proposital e separado: de a um token de
|
|
7
|
+
* PROVISIONAMENTO, nao ao token que o seu servidor usa todo dia. Quem tem
|
|
8
|
+
* este escopo pode redirecionar os seus eventos. O token padrao NAO o tem, e
|
|
9
|
+
* por isso estas chamadas devolvem `SemEscopo` com um token comum.
|
|
10
|
+
*/
|
|
11
|
+
export class Webhooks {
|
|
12
|
+
constructor(private readonly t: Transporte) {}
|
|
13
|
+
|
|
14
|
+
/** Destino atual e o PREFIXO do segredo, nunca o segredo. */
|
|
15
|
+
async atual(): Promise<Record<string, unknown>> {
|
|
16
|
+
return this.t.pedir("GET", "webhooks");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async definir(url: string, eventos: string[] = []): Promise<Record<string, unknown>> {
|
|
20
|
+
const corpo: Record<string, unknown> = { url };
|
|
21
|
+
if (eventos.length > 0) corpo.eventos = eventos;
|
|
22
|
+
return this.t.pedir("PUT", "webhooks", { corpo });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async remover(): Promise<Record<string, unknown>> {
|
|
26
|
+
return this.t.pedir("DELETE", "webhooks");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Gira o segredo e devolve o novo, UMA unica vez.
|
|
31
|
+
*
|
|
32
|
+
* Guarde na hora: a consulta nunca mais mostra o valor inteiro. E lembre
|
|
33
|
+
* que girar invalida a assinatura dos avisos em voo.
|
|
34
|
+
*/
|
|
35
|
+
async girarSegredo(): Promise<Record<string, unknown>> {
|
|
36
|
+
return this.t.pedir("POST", "webhooks/secret/rotate");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Dispara um evento de teste para o seu endpoint. */
|
|
40
|
+
async testar(url?: string): Promise<Record<string, unknown>> {
|
|
41
|
+
return this.t.pedir("POST", "webhooks/testar", { corpo: url ? { url } : null });
|
|
42
|
+
}
|
|
43
|
+
}
|