@jackiemacklein/nettz-utils
Version:
Serviços de imagem, e-mail, códigos de barras, utilitários numéricos e componentes React para apps Node.js com TypeScript
364 lines (363 loc) • 16.6 kB
JavaScript
;
/**
* @author Jackiê Macklein
* @company Onside tecnologia/Nettz
* @copyright Todos direitos reservados.
* @description Cliente HTTP para API de Transação Remota da EvoluServices.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createEvoPagamentosClient = createEvoPagamentosClient;
const errors_1 = require("./errors");
const createRemoteTransactionValidation_1 = require("./createRemoteTransactionValidation");
const types_1 = require("./types");
async function readResponseBody(res) {
try {
return await res.text();
}
catch {
return "";
}
}
const EVO_HTTP_BODY_PREVIEW = 2000;
const EVO_DEBUG_LOG_PREFIX = "[EvoPagamentos]";
const EVO_DEBUG_MAX_DEPTH = 10;
const EVO_DEBUG_MAX_ARRAY_ITEMS = 40;
const EVO_DEBUG_MAX_STRING = 2000;
function redactQueryToken(urlOrPath) {
return String(urlOrPath).replace(/([?&]token=)[^&]*/gi, "$1***");
}
function maskUsername(u) {
if (u == null || u === "")
return undefined;
if (u.length <= 2)
return `***(${u.length} chars)`;
return `${u.slice(0, 2)}…(${u.length} chars)`;
}
function maskSecret(s) {
if (s == null || s === "")
return undefined;
return `***(${s.length} chars)`;
}
function isSensitiveKey(key) {
const k = key.toLowerCase();
if (k === "apikey" || k === "api_key" || k === "password" || k === "secret" || k === "token" || k === "bearer" || k === "authorization") {
return "full";
}
if (k === "username" || k === "user" || k === "email")
return "partial";
return false;
}
/** Clona estrutura JSON para log: mascara segredos, URLs com token=, limita profundidade e tamanho de arrays/strings. */
function sanitizeForDebugLog(value, depth = 0) {
var _a;
if (depth > EVO_DEBUG_MAX_DEPTH)
return "[max-depth]";
if (value === null || value === undefined)
return value;
if (typeof value === "string") {
let s = value.length > EVO_DEBUG_MAX_STRING ? `${value.slice(0, EVO_DEBUG_MAX_STRING)}…` : value;
if (/^https?:\/\//i.test(s))
s = redactQueryToken(s);
return s;
}
if (typeof value === "number" || typeof value === "boolean")
return value;
if (typeof value !== "object")
return String(value);
if (Array.isArray(value)) {
const slice = value.slice(0, EVO_DEBUG_MAX_ARRAY_ITEMS);
const mapped = slice.map(item => sanitizeForDebugLog(item, depth + 1));
if (value.length > EVO_DEBUG_MAX_ARRAY_ITEMS) {
mapped.push(`… +${value.length - EVO_DEBUG_MAX_ARRAY_ITEMS} itens`);
}
return mapped;
}
const out = {};
for (const [key, v] of Object.entries(value)) {
const sens = isSensitiveKey(key);
if (sens === "full") {
out[key] = typeof v === "string" && v.length > 0 ? `***(${v.length} chars)` : "***";
continue;
}
if (sens === "partial" && typeof v === "string") {
out[key] = (_a = maskUsername(v)) !== null && _a !== void 0 ? _a : `***(${String(v).length})`;
continue;
}
if (key === "transaction" && v && typeof v === "object") {
out[key] = sanitizeForDebugLog(sanitizeTransactionBranch(v), depth + 1);
continue;
}
out[key] = sanitizeForDebugLog(v, depth + 1);
}
return out;
}
/** Campos extras da transaction (documento sempre mascarado). */
function sanitizeTransactionBranch(t) {
if (!t || typeof t !== "object")
return t;
const tr = { ...t };
if (typeof tr.callbackUrl === "string")
tr.callbackUrl = redactQueryToken(tr.callbackUrl);
if (tr.clientDocument != null && `${tr.clientDocument}` !== "")
tr.clientDocument = `***(${String(tr.clientDocument).length} dígitos)`;
if (typeof tr.clientName === "string" && tr.clientName.length > 0) {
const n = tr.clientName;
tr.clientName = n.length <= 2 ? `***(${n.length})` : `${n.slice(0, 2)}…(${n.length} chars)`;
}
return tr;
}
function parseAndSanitizeJsonBody(raw) {
if (raw == null || String(raw).trim() === "")
return undefined;
try {
return sanitizeForDebugLog(JSON.parse(String(raw)));
}
catch {
return redactQueryToken(String(raw).slice(0, EVO_DEBUG_MAX_STRING));
}
}
function safeTransactionLogPayload(body) {
var _a, _b;
const t = body === null || body === void 0 ? void 0 : body.transaction;
if (!t)
return { transaction: null };
const cb = t.callbackUrl ? redactQueryToken(t.callbackUrl) : undefined;
const clientName = t.clientName && t.clientName.length > 0
? t.clientName.length <= 2
? `***(${t.clientName.length})`
: `${t.clientName.slice(0, 2)}…(${t.clientName.length} chars)`
: undefined;
return {
merchantId: t.merchantId,
terminalId: t.terminalId,
value: t.value,
installments: t.installments,
installmentsCanChange: t.installmentsCanChange,
paymentBrand: t.paymentBrand,
callbackUrl: cb,
clientName,
clientDocument: t.clientDocument ? "***" : undefined,
clientEmail: t.clientEmail ? `***(${String(t.clientEmail).length} chars)` : undefined,
hasClientEmail: Boolean(t.clientEmail),
splits: (_b = (_a = t.splits) === null || _a === void 0 ? void 0 : _a.map(s => ({
code: s.code,
value: s.value,
chargeFees: s.chargeFees,
}))) !== null && _b !== void 0 ? _b : [],
};
}
/**
* Monta mensagem e código opcional a partir do corpo de erro HTTP da EvoluServices.
* @param path Caminho da requisição (ex.: `/remote/token`) — não inclui credenciais.
*/
function describeEvoHttpErrorBody(status, statusText, text, path) {
const trimmed = text.trim();
if (!trimmed) {
return {
message: `EvoPagamentos HTTP ${status} ${statusText} em \`${path}\` sem corpo na resposta. ` +
`Verifique EVOPAGAMENTOS_API_BASE_URL (sandbox: https://sandbox.evoluservices.com, produção: https://api.evoluservices.com), ` +
`EVOPAGAMENTOS_USERNAME, EVOPAGAMENTOS_API_KEY e se o serviço EvoluServices está disponível.`,
};
}
const parsed = (0, createRemoteTransactionValidation_1.tryParseEvoRemoteTransactionResponse)(text);
if (parsed === null || parsed === void 0 ? void 0 : parsed.error) {
return {
message: `EvoPagamentos HTTP ${status}: ${(0, createRemoteTransactionValidation_1.getEvoRemoteTransactionErrorMessage)(parsed.error)}`,
evoErrorCode: parsed.error,
};
}
try {
const j = JSON.parse(text);
const code = typeof j.error === "string" && j.error.trim() !== "" ? j.error.trim() : undefined;
const msg = (typeof j.message === "string" && j.message.trim()) ||
(typeof j.Message === "string" && j.Message.trim()) ||
(typeof j.detail === "string" && j.detail.trim()) ||
(typeof j.exception === "string" && j.exception.trim()) ||
"";
if (code && msg) {
const friendly = (0, createRemoteTransactionValidation_1.getEvoRemoteTransactionErrorMessage)(code);
return { message: `EvoPagamentos HTTP ${status}: ${friendly} (${msg})`, evoErrorCode: code };
}
if (code) {
return { message: `EvoPagamentos HTTP ${status}: ${(0, createRemoteTransactionValidation_1.getEvoRemoteTransactionErrorMessage)(code)}`, evoErrorCode: code };
}
if (msg) {
return { message: `EvoPagamentos HTTP ${status}: ${msg}` };
}
}
catch {
/* corpo não é JSON */
}
const preview = trimmed.length > EVO_HTTP_BODY_PREVIEW ? `${trimmed.slice(0, EVO_HTTP_BODY_PREVIEW)}…` : trimmed;
return { message: `EvoPagamentos HTTP ${status} ${statusText}: ${preview}` };
}
function createEvoPagamentosClient(config = {}) {
var _a, _b, _c;
const baseUrl = ((_a = config.baseUrl) !== null && _a !== void 0 ? _a : types_1.EVOPAGAMENTOS_DEFAULT_BASE_URL).replace(/\/$/, "");
const timeoutMs = (_b = config.timeoutMs) !== null && _b !== void 0 ? _b : 30000;
const fetchFn = (_c = config.fetchImpl) !== null && _c !== void 0 ? _c : fetch;
const debug = Boolean(config.debug);
const log = (...args) => {
if (debug) {
console.log(EVO_DEBUG_LOG_PREFIX, new Date().toISOString(), ...args);
}
};
log("client init", { baseUrl, timeoutMs });
async function request(path, init) {
var _a, _b, _c;
const method = (init === null || init === void 0 ? void 0 : init.method) || "GET";
const url = `${baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
const bearerHdr = (init === null || init === void 0 ? void 0 : init.headers) && typeof init.headers.Bearer === "string" ? init.headers.Bearer : undefined;
log("request", method, path, {
baseUrl,
hasBearer: Boolean(bearerHdr),
bearerLength: (_a = bearerHdr === null || bearerHdr === void 0 ? void 0 : bearerHdr.length) !== null && _a !== void 0 ? _a : 0,
hasJsonBody: Boolean(init === null || init === void 0 ? void 0 : init.body),
body: parseAndSanitizeJsonBody(typeof (init === null || init === void 0 ? void 0 : init.body) === "string" ? init.body : undefined),
});
const controller = new AbortController();
const t = setTimeout(() => controller.abort(), (_b = init === null || init === void 0 ? void 0 : init.timeoutMs) !== null && _b !== void 0 ? _b : timeoutMs);
try {
let res;
try {
res = await fetchFn(url, {
...init,
signal: (_c = init === null || init === void 0 ? void 0 : init.signal) !== null && _c !== void 0 ? _c : controller.signal,
headers: {
Accept: "application/json",
...((init === null || init === void 0 ? void 0 : init.body) ? { "Content-Type": "application/json" } : {}),
...init === null || init === void 0 ? void 0 : init.headers,
},
});
}
catch (e) {
log("fetch exceção", path, e instanceof Error ? e.message : String(e));
throw e;
}
const text = await readResponseBody(res);
log("response", path, res.status, res.ok, { bytes: text.length });
if (!res.ok) {
const { message, evoErrorCode } = describeEvoHttpErrorBody(res.status, res.statusText, text, path);
const preview = text.length > 800 ? `${redactQueryToken(text.slice(0, 800))}…` : redactQueryToken(text);
log("HTTP erro", path, res.status, evoErrorCode !== null && evoErrorCode !== void 0 ? evoErrorCode : "(sem código)", preview || "(vazio)");
try {
log("HTTP erro body (sanitizado)", path, sanitizeForDebugLog(JSON.parse(text)));
}
catch {
/* corpo não é JSON */
}
throw new errors_1.EvoPagamentosApiError(message, res.status, text, evoErrorCode);
}
if (!text || text.trim() === "") {
log("response corpo vazio", path);
return undefined;
}
try {
const parsed = JSON.parse(text);
log("response body (sanitizado)", path, sanitizeForDebugLog(parsed));
return parsed;
}
catch {
log("JSON inválido", path, text.slice(0, 400));
throw new errors_1.EvoPagamentosApiError("EvoPagamentos resposta não é JSON válido", res.status, text);
}
}
finally {
clearTimeout(t);
}
}
return {
baseUrl,
debug,
async authenticate(body) {
var _a, _b;
log("authenticate params (sanitizado)", {
auth: {
username: maskUsername(body.username),
apiKey: maskSecret(body.apiKey),
},
});
const data = await request("/remote/token", {
method: "POST",
body: JSON.stringify({ auth: body }),
});
const bearerLen = (_b = (_a = data === null || data === void 0 ? void 0 : data.Bearer) === null || _a === void 0 ? void 0 : _a.trim().length) !== null && _b !== void 0 ? _b : 0;
log("authenticate ok", { bearerLength: bearerLen });
return data;
},
async listTerminals(merchantCode, bearerToken) {
var _a;
log("listTerminals params", {
merchantCode,
bearerLength: (_a = bearerToken === null || bearerToken === void 0 ? void 0 : bearerToken.length) !== null && _a !== void 0 ? _a : 0,
});
const list = await request(`/remote/merchants/${encodeURIComponent(merchantCode)}/terminals`, {
method: "GET",
headers: { Bearer: bearerToken },
});
log("listTerminals ok", { count: Array.isArray(list) ? list.length : "?" });
return list;
},
async createRemoteTransaction(body, bearerToken) {
var _a;
try {
(0, createRemoteTransactionValidation_1.assertValidCreateRemoteTransactionBody)(body);
}
catch (e) {
log("createRemoteTransaction validação local falhou", {
message: e instanceof Error ? e.message : String(e),
evoErrorCode: e instanceof errors_1.EvoPagamentosApiError ? e.evoErrorCode : undefined,
bodyParams: safeTransactionLogPayload(body),
});
throw e;
}
log("createRemoteTransaction params (sanitizado)", {
bearerLength: (_a = bearerToken === null || bearerToken === void 0 ? void 0 : bearerToken.length) !== null && _a !== void 0 ? _a : 0,
transaction: safeTransactionLogPayload(body),
});
try {
const data = await request("/remote/transaction", {
method: "POST",
headers: { Bearer: bearerToken },
body: JSON.stringify(body),
});
(0, createRemoteTransactionValidation_1.assertEvoCreateRemoteTransactionSuccess)(data);
log("createRemoteTransaction ok", {
transactionId: data.transactionId,
success: data.success,
});
return data;
}
catch (err) {
if (err instanceof errors_1.EvoPagamentosApiError) {
log("createRemoteTransaction falhou", {
status: err.status,
evoErrorCode: err.evoErrorCode,
message: err.message,
});
}
if (err instanceof errors_1.EvoPagamentosApiError && err.evoErrorCode === undefined) {
const parsed = (0, createRemoteTransactionValidation_1.tryParseEvoRemoteTransactionResponse)(err.body);
if (parsed === null || parsed === void 0 ? void 0 : parsed.error) {
const msg = (0, createRemoteTransactionValidation_1.getEvoRemoteTransactionErrorMessage)(parsed.error);
throw new errors_1.EvoPagamentosApiError(`EvoPagamentos: ${msg}`, err.status, err.body, parsed.error);
}
}
throw err;
}
},
async deleteRemoteTransaction(remoteTransactionId, bearerToken) {
var _a;
log("deleteRemoteTransaction params", {
remoteTransactionId,
bearerLength: (_a = bearerToken === null || bearerToken === void 0 ? void 0 : bearerToken.length) !== null && _a !== void 0 ? _a : 0,
});
const out = await request(`/remote/${encodeURIComponent(remoteTransactionId)}`, {
method: "DELETE",
headers: { Bearer: bearerToken },
});
log("deleteRemoteTransaction ok", { success: out === null || out === void 0 ? void 0 : out.success });
return out;
},
};
}