UNPKG

@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
"use strict"; /** * @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; }, }; }