next-gs
Version:
NPM package for building a React+NextJS+Prisma admin application.
306 lines (253 loc) • 7.45 kB
text/typescript
import cardValidator from "card-validator";
import Environment from "erede-node/lib/environment";
import Transaction from "erede-node/lib/transaction";
import RedeError from "erede-node/lib/exception/RedeError";
import eRede from "erede-node/lib/erede";
import Url from "erede-node/lib/url";
import Store from "erede-node/lib/store";
import { encrypt, decrypt } from "./crypto";
import { fetchJson } from "./fetch";
import _ from "./funcs";
export type eRedeConfig = {
token: string;
pv: string;
};
export type eRedeCardInfo = {
brand: string;
cvv: string;
expMonth: string;
expYear: string;
holder: string;
number: string;
};
export type eRedeCharge = {
ref: string;
amount: number;
cardToken: string;
cardType: string;
};
export type eRedeResponse = {
success?: boolean;
statusCode?: string;
statusReason: string;
};
export type eRedeSuccessResponse = eRedeResponse & {
nsu: string;
tid: string;
reference: string;
refundId: string;
refundDate: string;
};
const getErrorMessage = (error: any) => {
if (error instanceof Error) return error.message;
switch (Number.parseInt(error.returnCode)) {
case 80:
case 111:
return "Não autorizado. (Saldo/limite insuficiente).";
case 42:
return "Referência: o número do pedido já existe.";
case 58:
return "Não autorizado. Entre em contato com o emissor do cartão.";
case 55:
return "Nome do titular do cartão: tamanho do parâmetro inválido.";
case 64:
return "Transação não processada. Tente novamente.";
case 69:
return "Transação não permitida para este produto ou serviço.";
case 71:
return "Valor: Formato de parâmetro inválido.";
case 110:
return "Não autorizado. Tipo de transação não permitido para este cartão.";
case 105:
return "Não autorizado. Cartao restrito.";
case 109:
return "Não autorizado. Cartão inexistente.";
case 112:
return "Não autorizado. A data de validade expirou.";
case 119:
return "Não autorizado. Código de segurança inválido.";
case 353:
return "Transação não encontrada.";
case 365:
return "Reembolso parcial não disponível.";
case 354:
return "Transação com prazo expirado para reembolso.";
case 355:
return "Transação já cancelada.";
default:
return error.returnMessage || "Payment gateway server error (eRede)";
}
};
export const fmtAmount = (amount: number) =>
Number.parseInt((amount * 100).toString());
export const isRefundOk = (status: string) =>
status === "359" || status === "360";
const createSuccessResponse = (resp: any, refund?: boolean) => {
const {
returnCode: statusCode,
returnMessage,
nsu,
tid,
reference,
refundId,
refundDateTime: refundDate,
} = resp;
const success = refund ? isRefundOk(statusCode) : statusCode === "00";
const statusReason = success
? `${
refund
? `Transação: ${refundId}`
: `Transação: ${tid}, NSU: ${nsu}, Ref: ${reference}`
}. ${returnMessage}`
: undefined;
return {
success,
statusCode,
statusReason,
nsu,
tid,
reference,
refundId,
refundDate,
} as eRedeSuccessResponse;
};
const createFailureResponse = (error: any) => {
console.log(error);
return {
statusCode: error.returnCode,
statusReason: getErrorMessage(error),
} as eRedeResponse;
};
export class eRedeToken {
encrypt({ brand, cvv, expMonth, expYear, holder, number }: eRedeCardInfo) {
return encrypt(
`${brand}-${cvv}-${expMonth}-${expYear}-${holder}-${number}`,
);
}
decrypt(plain: string) {
if (!plain) return;
const s = decrypt(plain);
const [brand, cvv, expMonth, expYear, holder, number] = s.split("-");
return { brand, cvv, expMonth, expYear, holder, number } as eRedeCardInfo;
}
}
export class eRedeService {
public store;
public erede;
static cardToken = new eRedeToken();
static checkCardNumber = (number: string) => {
number = _.numbers(number);
const { card, isValid } = cardValidator.number(number);
return { number, brand: _.toUpper(card?.niceType), isValid };
};
constructor(config: eRedeConfig) {
this.store = new Store(config.token, config.pv, Environment.production());
this.erede = new eRede(this.store);
}
customRequest(method: string, body: any) {
const path = this.store.environment.endpoint + "/transactions";
const options = {
method,
port: 443,
body,
auth: this.store.filiation + ":" + this.store.token,
};
return fetchJson(path, options)
.then(async (resp) => {
const json = await resp.json();
const { status } = resp;
return { status, json };
})
.then(({ status, json }) => {
let resp = json as any;
if (status >= 400) {
if (!resp || resp.returnMessage === undefined) {
resp = {
returnMessage: "Alguma coisa aconteceu",
returnCode: "-1",
};
}
return Promise.reject(
new RedeError(resp.returnMessage, resp.returnCode),
);
}
return Transaction.fromJSON(resp);
});
}
async findPayment(ref: string) {
try {
const resp = await this.erede.getByReference(ref);
return createSuccessResponse(resp);
} catch (error) {
return createFailureResponse(error);
}
}
async payWithCredit(charge: eRedeCharge, card: eRedeCardInfo) {
const trx = new Transaction(
fmtAmount(charge.amount),
charge.ref,
1,
).creditCard(
card.number,
card.cvv,
card.expMonth,
card.expYear,
card.holder,
);
try {
const resp: eRedeResponse = await this.customRequest("POST", trx);
return createSuccessResponse(resp);
} catch (error) {
if ((error as any)?.returnCode === "42") {
return await this.findPayment(charge.ref);
}
return createFailureResponse(error);
}
}
async payWithDebit(charge: eRedeCharge, card: eRedeCardInfo) {
const transaction = new Transaction(fmtAmount(charge.amount), charge.ref)
.debitCard(
card.number,
card.cvv,
parseInt(card.expMonth),
parseInt(card.expYear),
card.holder,
)
.setThreeDSecure()
.addUrl(
"http://dev-api-cl.us-east-1.elasticbeanstalk.com/recurrent-payments/notification",
Url.THREE_D_SECURE_SUCCESS,
);
try {
const resp = await this.erede.create(transaction);
if (resp.returnCode === "220") {
await fetch(`${process.env.LAMBDA_APP_API_URL}/create-debit-payment`, {
method: "POST",
body: JSON.stringify(resp),
headers: {
"Content-Type": "application/json",
},
});
throw new Error("Qualquer coisa");
}
return createSuccessResponse(resp);
} catch (error) {
return createFailureResponse(error as eRedeResponse);
}
}
async payment(charge: eRedeCharge) {
const card = eRedeService.cardToken.decrypt(charge.cardToken);
if (!card) throw new Error("Invalid card token.");
return charge.cardType === "C"
? await this.payWithCredit(charge, card)
: await this.payWithDebit(charge, card);
}
async refund(tid: string, amount: number) {
try {
const resp = await this.erede.cancel({ tid, amount: fmtAmount(amount) });
return createSuccessResponse(resp, true);
} catch (error) {
return createFailureResponse(error);
}
}
}