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

179 lines (178 loc) 10.2 kB
"use strict"; /** * @author Jackiê Macklein * @company Onside tecnologia/Nettz * @copyright Todos direitos reservados. * @description Validação client-side da criação de transação remota (bandeira, parcelas, valor, splits). * Referência: https://evoluservices.github.io/evoluservices-developer/docs/remote-transaction/create-remote-transaction/how-to-create-remote-transaction */ Object.defineProperty(exports, "__esModule", { value: true }); exports.EvoRemoteTransactionErrorCode = void 0; exports.getEvoRemoteTransactionErrorMessage = getEvoRemoteTransactionErrorMessage; exports.tryParseEvoRemoteTransactionResponse = tryParseEvoRemoteTransactionResponse; exports.assertValidCreateRemoteTransactionBody = assertValidCreateRemoteTransactionBody; exports.assertEvoCreateRemoteTransactionSuccess = assertEvoCreateRemoteTransactionSuccess; const errors_1 = require("./errors"); const types_1 = require("./types"); const KNOWN_BRANDS = new Set(Object.values(types_1.EvoPaymentBrand)); /** Bandeiras tratadas como débito na documentação (parcela única). */ const DEBIT_BRANDS = new Set([types_1.EvoPaymentBrand.MAESTRO, types_1.EvoPaymentBrand.VISA_ELECTRON, types_1.EvoPaymentBrand.ELO_DEBITO]); const MERCHANT_ID_RE = /^[0-9A-Za-z]+$/; const MONEY_RE = /^\d+\.\d{2}$/; /** Códigos `error` documentados na criação de transação remota (resposta de erro). */ exports.EvoRemoteTransactionErrorCode = { PAYMENT_BRAND_ID_INVALID: "PAYMENT_BRAND_ID_INVALID", INSTALLMENTS_INVALID_FOR_DEBIT: "INSTALLMENTS_INVALID_FOR_DEBIT", INVALID_PAYMENT_BRAND: "INVALID_PAYMENT_BRAND", INVALID_INSTALLMENTS_QUANTITY_OR_VALUE: "INVALID_INSTALLMENTS_QUANTITY_OR_VALUE", MERCHANT_ID_INVALID: "MERCHANT_ID_INVALID", TERMINAL_ID_INVALID: "TERMINAL_ID_INVALID", MERCHANT_TERMINAL_INVALID: "MERCHANT_TERMINAL_INVALID", VALUE_FIELD_INVALID: "VALUE_FIELD_INVALID", NAME_CLIENT_INVALID: "NAME_CLIENT_INVALID", DOCUMENT_INVALID: "DOCUMENT_INVALID", SPLIT_SUM_GREATER_THAN_TRANSACTION_VALUE: "SPLIT_SUM_GREATER_THAN_TRANSACTION_VALUE", SUPPLIER_NOT_FOUND: "SUPPLIER_NOT_FOUND", SUPPLIER_INVALID: "SUPPLIER_INVALID", }; const ERROR_MESSAGES_PT = { EMPTY_RESPONSE: "Resposta vazia do servidor.", VALIDATION: "Dados da transação inválidos (validação local).", /** Resposta comum em `/remote/token` quando usuário ou API key não existem nesse ambiente. */ USER_TOKEN_NOT_FOUND: "Usuário ou chave de API não reconhecidos pela EvoluServices neste ambiente. Confira EVOPAGAMENTOS_USERNAME e EVOPAGAMENTOS_API_KEY e se EVOPAGAMENTOS_API_BASE_URL (sandbox vs produção) corresponde à conta.", /** Resposta comum em `/remote/token` quando o usuário existe mas o campo `apiKey` (senha da API) está errado. */ PASSWORD_INVALID: "Chave de API inválida para este usuário na EvoluServices. Confira EVOPAGAMENTOS_API_KEY no painel (sandbox/produção) e se não há espaços ou aspas extras no .env.", [exports.EvoRemoteTransactionErrorCode.PAYMENT_BRAND_ID_INVALID]: "Bandeira de pagamento inválida ou inexistente.", [exports.EvoRemoteTransactionErrorCode.INSTALLMENTS_INVALID_FOR_DEBIT]: "Cartão de débito não pode ter mais de uma parcela.", [exports.EvoRemoteTransactionErrorCode.INVALID_PAYMENT_BRAND]: "Bandeira não habilitada para o estabelecimento.", [exports.EvoRemoteTransactionErrorCode.INVALID_INSTALLMENTS_QUANTITY_OR_VALUE]: "Quantidade de parcelas ou valor mínimo da parcela não aceito.", [exports.EvoRemoteTransactionErrorCode.MERCHANT_ID_INVALID]: "Identificador do estabelecimento (merchantId) inválido.", [exports.EvoRemoteTransactionErrorCode.TERMINAL_ID_INVALID]: "Identificador do terminal inválido.", [exports.EvoRemoteTransactionErrorCode.MERCHANT_TERMINAL_INVALID]: "Terminal não está apto a receber transações remotas.", [exports.EvoRemoteTransactionErrorCode.VALUE_FIELD_INVALID]: "Formato do campo value inválido (use decimal com duas casas, ex.: 10.00).", [exports.EvoRemoteTransactionErrorCode.NAME_CLIENT_INVALID]: "Campo clientName inválido.", [exports.EvoRemoteTransactionErrorCode.DOCUMENT_INVALID]: "Campo clientDocument inválido (apenas números, sem máscara).", [exports.EvoRemoteTransactionErrorCode.SPLIT_SUM_GREATER_THAN_TRANSACTION_VALUE]: "A soma dos splits ultrapassa o valor da transação.", [exports.EvoRemoteTransactionErrorCode.SUPPLIER_NOT_FOUND]: "Código de beneficiário do split não encontrado.", [exports.EvoRemoteTransactionErrorCode.SUPPLIER_INVALID]: "Beneficiário do split não está vinculado ao estabelecimento.", }; function parseMoney(s) { const m = String(s).match(/^(\d+)\.(\d{2})$/); if (!m) return Number.NaN; return parseInt(m[1], 10) + parseInt(m[2], 10) / 100; } function throwValidation(message) { const body = JSON.stringify({ error: "VALIDATION", message }); throw new errors_1.EvoPagamentosApiError(message, 400, body, "VALIDATION"); } /** Mensagem amigável para o código retornado pela API (fallback: o próprio código). */ function getEvoRemoteTransactionErrorMessage(code) { var _a; return (_a = ERROR_MESSAGES_PT[code]) !== null && _a !== void 0 ? _a : `Erro: ${code}`; } /** Tenta extrair `error` / `success` de um corpo JSON da EvoluServices. */ function tryParseEvoRemoteTransactionResponse(body) { try { const j = JSON.parse(body); if (j && (typeof j.error === "string" || typeof j.success === "string")) { return j; } } catch { /* ignore */ } return undefined; } /** * Valida o body antes do POST. Lança `EvoPagamentosApiError` com `status` 400 e corpo JSON com `message`. */ function assertValidCreateRemoteTransactionBody(body) { var _a, _b, _c, _d; const t = body === null || body === void 0 ? void 0 : body.transaction; if (!t) { throwValidation("Corpo inválido: falta transaction."); } const merchantId = String((_a = t.merchantId) !== null && _a !== void 0 ? _a : "").trim(); if (!merchantId) { throwValidation("merchantId é obrigatório."); } // if (!MERCHANT_ID_RE.test(merchantId)) { // throwValidation("merchantId inválido: use apenas letras e números (conforme documentação)."); // } const valueStr = String((_b = t.value) !== null && _b !== void 0 ? _b : "").trim(); if (!valueStr) { throwValidation("value é obrigatório."); } if (!MONEY_RE.test(valueStr)) { throwValidation('value inválido: use string decimal com duas casas (ex.: "10.00").'); } const installments = t.installments; const installmentsSpecified = installments !== undefined && installments !== null && typeof installments === "number" && !Number.isNaN(installments); if (installmentsSpecified && installments < 1) { throwValidation("installments deve ser >= 1 quando informado."); } const brandRaw = t.paymentBrand; const brand = brandRaw === undefined || brandRaw === null ? "" : String(brandRaw).trim(); if (installmentsSpecified && brand === "") { throwValidation("paymentBrand é obrigatório quando installments é informado (documentação EvoluServices)."); } if (brand !== "" && !KNOWN_BRANDS.has(brand)) { throwValidation(`paymentBrand desconhecido: "${brand}". Use um dos códigos da tabela oficial (ex.: EvoPaymentBrand).`); } if (brand !== "" && DEBIT_BRANDS.has(brand) && installmentsSpecified && installments > 1) { throwValidation("Cartão de débito não pode ter mais de uma parcela (use installments 1 ou omita parcelas)."); } const cb = t.callbackUrl; if (cb !== undefined && cb !== null && String(cb).trim() !== "") { const u = String(cb).trim(); if (!/^https:\/\/.+/i.test(u)) { throwValidation("callbackUrl deve ser HTTPS (documentação EvoluServices)."); } } const doc = t.clientDocument; if (doc !== undefined && doc !== null && String(doc).trim() !== "") { if (!/^[0-9]+$/.test(String(doc).trim())) { throwValidation("clientDocument deve conter apenas números (sem pontuação)."); } } const splits = t.splits; if (splits && splits.length > 0) { const total = parseMoney(valueStr); if (Number.isNaN(total)) { throwValidation("Não foi possível interpretar value para validar splits."); } let sum = 0; for (let i = 0; i < splits.length; i++) { const s = splits[i]; const v = String((_c = s === null || s === void 0 ? void 0 : s.value) !== null && _c !== void 0 ? _c : "").trim(); if (!MONEY_RE.test(v)) { throwValidation(`splits[${i}].value inválido: use decimal com duas casas (ex.: \"2.00\").`); } sum += parseMoney(v); if (Number.isNaN(sum)) { throwValidation(`splits[${i}].value inválido.`); } const c = String((_d = s === null || s === void 0 ? void 0 : s.code) !== null && _d !== void 0 ? _d : "").trim(); if (!/^[0-9]+$/.test(c)) { throwValidation(`splits[${i}].code inválido: use apenas dígitos (conforme documentação).`); } } if (sum > total + 0.0001) { throwValidation("A soma dos valores em splits não pode ultrapassar o value da transação."); } } } /** Se a API retornou JSON com success !== true, lança erro com `evoErrorCode`. */ function assertEvoCreateRemoteTransactionSuccess(data) { var _a; if (data == null) { throw new errors_1.EvoPagamentosApiError("EvoPagamentos: resposta vazia ao criar transação remota.", 502, "", "EMPTY_RESPONSE"); } if (String(data.success).toLowerCase() === "true") { return; } const code = (_a = data === null || data === void 0 ? void 0 : data.error) !== null && _a !== void 0 ? _a : "UNKNOWN"; const msg = getEvoRemoteTransactionErrorMessage(code); throw new errors_1.EvoPagamentosApiError(`EvoPagamentos: ${msg}`, 422, JSON.stringify(data !== null && data !== void 0 ? data : {}), code); }