zkPix v0
This commit is contained in:
283
bbpay.ts
Normal file
283
bbpay.ts
Normal file
@@ -0,0 +1,283 @@
|
||||
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}`);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user