283 lines
9.3 KiB
TypeScript
283 lines
9.3 KiB
TypeScript
import dotenv from 'dotenv';
|
|
import express, { Request, Response } from 'express';
|
|
import cors from 'cors';
|
|
import axios, { AxiosInstance } from 'axios';
|
|
import { ClientCredentials, Token } from 'simple-oauth2';
|
|
import { ethers } from 'ethers';
|
|
import bs58 from 'bs58';
|
|
import { Wallet } from 'ethers';
|
|
import { toWei } from 'web3-utils';
|
|
import https from 'https';
|
|
import http from 'http';
|
|
import debug from 'debug';
|
|
import axiosDebugLog from 'axios-debug-log';
|
|
|
|
// Load environment variables from .env file
|
|
dotenv.config();
|
|
|
|
// Create a debug instance
|
|
const log = debug('bbpay');
|
|
|
|
// Enable debug messages based on the DEBUG environment variable
|
|
if (process.env.DEBUG) {
|
|
debug.enable('bbpay,simple-oauth2,axios');
|
|
}
|
|
|
|
// Enable debug messages for Axios requests
|
|
axiosDebugLog({
|
|
request: function (debug, config) {
|
|
debug('Request:', config);
|
|
},
|
|
response: function (debug, response) {
|
|
debug('Response:', response);
|
|
},
|
|
error: function (debug, error) {
|
|
debug('Error:', error);
|
|
},
|
|
});
|
|
|
|
const app = express();
|
|
app.use(cors());
|
|
app.use(express.json());
|
|
|
|
class BBPay {
|
|
protected oauth: AxiosInstance;
|
|
protected cert: string;
|
|
protected verifySsl: string;
|
|
protected baseUrl: string;
|
|
protected params: any;
|
|
protected scope: string[];
|
|
|
|
protected async setupOauth(): Promise<void> {
|
|
log('Setting up OAuth...');
|
|
const client = new ClientCredentials({
|
|
client: {
|
|
id: process.env.CLIENT_ID,
|
|
secret: process.env.CLIENT_SECRET,
|
|
},
|
|
auth: {
|
|
tokenHost: process.env.ITP_OAUTH_URL, // Ensure this is set to the correct token endpoint URL
|
|
},
|
|
});
|
|
|
|
this.cert = 'cert.pem';
|
|
this.key = 'key.pem'
|
|
this.verifySsl = 'bb.pem';
|
|
this.baseUrl = process.env.ITP_API_URL;
|
|
|
|
this.params = {
|
|
numeroConvenio: 701,
|
|
'gw-dev-app-key': process.env.DEV_APP_KEY,
|
|
};
|
|
|
|
this.scope = [
|
|
'checkout.solicitacoes-requisicao',
|
|
'checkout.participantes-requisicao',
|
|
'checkout.solicitacoes-info',
|
|
'checkout.participantes-info',
|
|
];
|
|
|
|
const tokenParams = {
|
|
scope: this.scope.join(' '),
|
|
};
|
|
|
|
log('Fetching access token...');
|
|
const accessToken: Token = await client.getToken(tokenParams);
|
|
log('Access token fetched successfully.');
|
|
|
|
this.oauth = axios.create({
|
|
baseURL: this.baseUrl,
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken.token.access_token}`,
|
|
},
|
|
httpsAgent: new https.Agent({
|
|
cert: this.cert,
|
|
key: this.key,
|
|
rejectUnauthorized: false,
|
|
}),
|
|
});
|
|
}
|
|
|
|
public async init(): Promise<void> {
|
|
await this.setupOauth();
|
|
}
|
|
}
|
|
|
|
class Register extends BBPay {
|
|
public async post(req: Request, res: Response): Promise<void> {
|
|
log('Registering participant...');
|
|
const data = req.body;
|
|
const body = {
|
|
numeroConvenio: 701,
|
|
nomeParticipante: data.chainID,
|
|
tipoDocumento: data.tipoDocumento,
|
|
numeroDocumento: data.numeroDocumento,
|
|
numeroConta: data.numeroConta,
|
|
numeroAgencia: data.numeroAgencia,
|
|
tipoConta: data.tipoConta,
|
|
codigoIspb: data.codigoIspb, // Código identificador do Sistema de Pagamentos Brasileiro. Atualmente aceitamos apenas Banco do Brasil, codigoIspb igual a 0
|
|
};
|
|
|
|
try {
|
|
log('Sending request to register participant...');
|
|
const response = await this.oauth.post('/participantes', body, {
|
|
params: this.params,
|
|
});
|
|
|
|
if (response.status !== 201) {
|
|
log('Upstream error:', response.status);
|
|
res.status(response.status).send('Upstream error');
|
|
return;
|
|
}
|
|
|
|
log('Participant registered successfully.');
|
|
res.json(response.data);
|
|
} catch (error) {
|
|
log('Internal server error:', error);
|
|
res.status(500).send('Internal server error');
|
|
}
|
|
}
|
|
}
|
|
|
|
class Request extends BBPay {
|
|
public async post(req: Request, res: Response): Promise<void> {
|
|
log('Creating request...');
|
|
const data = req.body;
|
|
const body = {
|
|
geral: {
|
|
numeroConvenio: 701,
|
|
pagamentoUnico: true,
|
|
descricaoSolicitacao: 'P2Pix',
|
|
valorSolicitacao: data.amount,
|
|
codigoConciliacaoSolicitacao: data.lockid,
|
|
},
|
|
formasPagamento: [{ codigoTipoPagamento: 'PIX', quantidadeParcelas: 1 }],
|
|
repasse: {
|
|
tipoValorRepasse: 'Percentual',
|
|
recebedores: [{
|
|
identificadorRecebedor: data.pixTarget,
|
|
tipoRecebedor: 'Participante',
|
|
valorRepasse: 100,
|
|
}],
|
|
},
|
|
};
|
|
|
|
try {
|
|
log('Sending request to create request...');
|
|
const response = await this.oauth.post('/solicitacoes', body, {
|
|
params: this.params,
|
|
});
|
|
|
|
if (response.status !== 201) {
|
|
log('Upstream error:', response.status);
|
|
res.status(response.status).send('Upstream error');
|
|
return;
|
|
}
|
|
|
|
log('Request created successfully.');
|
|
res.json(response.data);
|
|
} catch (error) {
|
|
log('Internal server error:', error);
|
|
res.status(500).send('Internal server error');
|
|
}
|
|
}
|
|
}
|
|
|
|
class Release extends BBPay {
|
|
public async get(req: Request, res: Response): Promise<void> {
|
|
const numeroSolicitacao = req.params.numeroSolicitacao;
|
|
log(`Releasing request ${numeroSolicitacao}...`);
|
|
|
|
try {
|
|
log('Fetching request details...');
|
|
const response = await this.oauth.get(`/solicitacoes/${numeroSolicitacao}`, {
|
|
params: this.params,
|
|
});
|
|
|
|
if (response.status !== 200) {
|
|
log('Upstream error:', response.status);
|
|
res.status(response.status).send('Upstream error');
|
|
return;
|
|
}
|
|
|
|
const data = response.data;
|
|
const numeroParticipante = data.repasse.recebedores[0].identificadorRecebedor;
|
|
const pixTimestamp = ethers.utils.solidityPack(['bytes32'], [ethers.utils.hexZeroPad(bs58.decode(data.informacoesPix.txId),32)]);
|
|
const valorSolicitacao = toWei(data.valorSolicitacao, 'ether');
|
|
const codigoEstadoSolicitacao = data.codigoEstadoSolicitacao;
|
|
|
|
if (codigoEstadoSolicitacao !== 1) {
|
|
log('Pix not paid.');
|
|
res.status(204).send('Pix not paid');
|
|
return;
|
|
}
|
|
|
|
log('Fetching participant details...');
|
|
const participantResponse = await this.oauth.get(`/participantes/${numeroParticipante}`, {
|
|
params: this.params,
|
|
});
|
|
|
|
if (participantResponse.status !== 200) {
|
|
log('Upstream error:', participantResponse.status);
|
|
res.status(participantResponse.status).send('Upstream error');
|
|
return;
|
|
}
|
|
|
|
const chainID = participantResponse.data.nomeParticipante;
|
|
const packed = ethers.utils.solidityPack(['bytes32', 'uint80', 'bytes32'], [
|
|
ethers.utils.hexZeroPad(ethers.utils.toUtf8Bytes(`${chainID}-${numeroParticipante}`),32),
|
|
ethers.BigNumber.from(valorSolicitacao),
|
|
ethers.utils.hexZeroPad(pixTimestamp,32),
|
|
]);
|
|
const signable = ethers.utils.keccak256(packed);
|
|
const wallet = new Wallet(process.env.PRIVATE_KEY);
|
|
const signature = await wallet.signMessage(signable);
|
|
|
|
log('Request released successfully.');
|
|
res.json({
|
|
pixTarget: `${chainID}-${numeroParticipante}`,
|
|
amount: valorSolicitacao.toString(),
|
|
pixTimestamp: `0x${pixTimestamp.toString('hex')}`,
|
|
signature: `0x${signature}`,
|
|
});
|
|
} catch (error) {
|
|
log('Internal server error:', error);
|
|
res.status(500).send('Internal server error');
|
|
}
|
|
}
|
|
}
|
|
|
|
// (CPF, nome, conta) -> participantID
|
|
// should be called before deposit
|
|
const register = new Register();
|
|
app.post('/register', async (req: Request, res: Response) => {
|
|
await register.init();
|
|
await register.post(req, res);
|
|
});
|
|
|
|
// (amount,pixtarget) -> requestID, QRcodeText
|
|
// should be called after lock
|
|
const request = new Request();
|
|
app.post('/request', async (req: Request, res: Response) => {
|
|
await request.init();
|
|
await request.post(req, res);
|
|
});
|
|
|
|
// (requestID) -> sig(pixTarget, amount, pixTimestamp)
|
|
// should be called before release
|
|
const release = new Release();
|
|
app.get('/release/:numeroSolicitacao', async (req: Request, res: Response) => {
|
|
await release.init();
|
|
await release.get(req, res);
|
|
});
|
|
|
|
if (process.env.DEBUG) {
|
|
app.listen(process.env.PORT || 5000, () => {
|
|
log(`Server running on port ${process.env.PORT || 5000}`);
|
|
});
|
|
} else {
|
|
const server = http.createServer(app);
|
|
server.listen(process.env.PORT || 5000, () => {
|
|
log(`Server running on port ${process.env.PORT || 5000}`);
|
|
});
|
|
} |