@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
JavaScript
;
/**
* @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);
}