efi-easy-pix
Version:
A simple Node.js module for integrating with Efipay's PIX API
302 lines (250 loc) • 9.94 kB
JavaScript
const axios = require('axios');
const fs = require('fs-extra');
const https = require('https');
const { QRCode } = require('./QRCode');
class EfipayClient {
constructor(config) {
if (!config) {
throw new Error('Configuration object is required');
}
if (!config.clientId || !config.clientSecret || !config.certificatePath) {
throw new Error('clientId, clientSecret, and certificatePath are required');
}
this.config = {
clientId: config.clientId,
clientSecret: config.clientSecret,
certificatePath: config.certificatePath,
sandbox: config.sandbox || false,
baseUrl: config.sandbox
? 'https://pix-h.api.efipay.com.br'
: 'https://pix.api.efipay.com.br'
};
this.accessToken = null;
this.tokenExpiration = null;
}
async getAccessToken() {
if (this.accessToken && this.tokenExpiration > Date.now()) {
return this.accessToken;
}
const auth = Buffer.from(`${this.config.clientId}:${this.config.clientSecret}`).toString('base64');
const certificate = await fs.readFile(this.config.certificatePath);
const httpsAgent = new https.Agent({
pfx: certificate,
passphrase: ''
});
try {
const response = await axios({
method: 'POST',
url: `${this.config.baseUrl}/oauth/token`,
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json'
},
data: {
grant_type: 'client_credentials'
},
httpsAgent
});
this.accessToken = response.data.access_token;
this.tokenExpiration = Date.now() + (response.data.expires_in * 1000);
return this.accessToken;
} catch (error) {
throw new Error(`Failed to get access token: ${error.message}`);
}
}
async request(method, endpoint, data = null, headers = {}) {
const token = await this.getAccessToken();
const certificate = await fs.readFile(this.config.certificatePath);
const httpsAgent = new https.Agent({
pfx: certificate,
passphrase: ''
});
try {
const response = await axios({
method,
url: `${this.config.baseUrl}${endpoint}`,
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
...headers
},
data,
httpsAgent
});
return {
data: response.data,
headers: response.headers
};
} catch (error) {
throw new Error(`API request failed: ${error.message}`);
}
}
// PIX Charge Methods
async createCharge(chargeData) {
return this.request('POST', '/v2/cob', chargeData);
}
async createChargeWithTxid(txid, chargeData) {
return this.request('PUT', `/v2/cob/${txid}`, chargeData);
}
async updateCharge(txid, chargeData) {
return this.request('PATCH', `/v2/cob/${txid}`, chargeData);
}
async getCharge(txid, revision = null) {
const endpoint = revision
? `/v2/cob/${txid}?revisao=${revision}`
: `/v2/cob/${txid}`;
return this.request('GET', endpoint);
}
async listCharges(params) {
if (!params.inicio || !params.fim) {
throw new Error('inicio and fim parameters are required');
}
const queryParams = new URLSearchParams(params).toString();
return this.request('GET', `/v2/cob?${queryParams}`);
}
// PIX Charge with Due Date Methods
async createDueDateCharge(txid, chargeData) {
if (!chargeData.calendario?.dataDeVencimento) {
throw new Error('dataDeVencimento is required in calendario object');
}
return this.request('PUT', `/v2/cobv/${txid}`, chargeData);
}
async updateDueDateCharge(txid, chargeData) {
return this.request('PATCH', `/v2/cobv/${txid}`, chargeData);
}
async getDueDateCharge(txid, revision = null) {
const endpoint = revision
? `/v2/cobv/${txid}?revisao=${revision}`
: `/v2/cobv/${txid}`;
return this.request('GET', endpoint);
}
async listDueDateCharges(params) {
if (!params.inicio || !params.fim) {
throw new Error('inicio and fim parameters are required');
}
const queryParams = new URLSearchParams(params).toString();
return this.request('GET', `/v2/cobv?${queryParams}`);
}
// PIX Send Methods
async sendPix(idEnvio, pixData) {
const response = await this.request('PUT', `/v3/gn/pix/${idEnvio}`, pixData);
return {
...response.data,
bucketSize: response.headers['bucket-size'],
retryAfter: response.headers['retry-after']
};
}
async getSentPixByE2eId(e2eId) {
return this.request('GET', `/v2/gn/pix/enviados/${e2eId}`);
}
async getSentPixByIdEnvio(idEnvio) {
return this.request('GET', `/v2/gn/pix/enviados/id-envio/${idEnvio}`);
}
async listSentPix(params) {
if (!params.inicio || !params.fim) {
throw new Error('inicio and fim parameters are required');
}
const queryParams = new URLSearchParams(params).toString();
return this.request('GET', `/v2/gn/pix/enviados?${queryParams}`);
}
// QR Code Methods
async getQrCodeDetails(pixCopiaECola) {
return this.request('POST', '/v2/gn/qrcodes/detalhar', { pixCopiaECola });
}
async payQrCode(idEnvio, paymentData) {
return this.request('PUT', `/v2/gn/pix/${idEnvio}/qrcode`, paymentData);
}
// PIX Transaction Methods
async getPixTransaction(e2eId, showBankCode = false) {
const endpoint = showBankCode
? `/v2/pix/${e2eId}?exibirCodigoBanco=true`
: `/v2/pix/${e2eId}`;
return this.request('GET', endpoint);
}
async listPixTransactions(params) {
if (!params.inicio || !params.fim) {
throw new Error('inicio and fim parameters are required');
}
const queryParams = new URLSearchParams(params).toString();
return this.request('GET', `/v2/pix?${queryParams}`);
}
async requestRefund(e2eId, refundId, refundData) {
if (!refundData.valor) {
throw new Error('valor is required for refund');
}
return this.request('PUT', `/v2/pix/${e2eId}/devolucao/${refundId}`, refundData);
}
async getRefund(e2eId, refundId) {
return this.request('GET', `/v2/pix/${e2eId}/devolucao/${refundId}`);
}
// QR Code Generation Methods
async generateQRCode(chargeData) {
const response = await this.request('POST', '/v2/loc', chargeData);
return new QRCode({
content: response.data.qrcode,
base64: response.data.qrcodeImage
});
}
async generateQRCodeWithDueDate(chargeData) {
const response = await this.request('POST', '/v2/loc', {
...chargeData,
calendario: {
...chargeData.calendario,
dataDeVencimento: chargeData.calendario.dataDeVencimento,
validadeAposVencimento: chargeData.calendario.validadeAposVencimento
}
});
return new QRCode({
content: response.data.qrcode,
base64: response.data.qrcodeImage
});
}
async getQRCodeDetails(qrCode) {
return this.request('GET', `/v2/loc/${qrCode.content}`);
}
// Webhook Methods
async configureWebhook(chave, webhookUrl, skipMtls = false) {
const headers = skipMtls ? { 'x-skip-mtls-checking': 'true' } : {};
return this.request('PUT', `/v2/webhook/${chave}`, { webhookUrl }, headers);
}
async getWebhookInfo(chave) {
return this.request('GET', `/v2/webhook/${chave}`);
}
async listWebhooks(params) {
if (!params.inicio || !params.fim) {
throw new Error('inicio and fim parameters are required');
}
const queryParams = new URLSearchParams(params).toString();
return this.request('GET', `/v2/webhook?${queryParams}`);
}
async deleteWebhook(chave) {
return this.request('DELETE', `/v2/webhook/${chave}`);
}
async resendWebhook(type, e2eids) {
return this.request('POST', '/v2/gn/webhook/reenviar', {
tipo: type,
e2eids: e2eids
});
}
// Automatic PIX Webhook Methods
async configureAutoPixWebhook(webhookUrl) {
return this.request('PUT', '/v2/webhookrec', { webhookUrl });
}
async getAutoPixWebhookInfo() {
return this.request('GET', '/v2/webhookrec');
}
async deleteAutoPixWebhook() {
return this.request('DELETE', '/v2/webhookrec');
}
// Automatic PIX Charge Webhook Methods
async configureAutoPixChargeWebhook(webhookUrl) {
return this.request('PUT', '/v2/webhookcobr', { webhookUrl });
}
async getAutoPixChargeWebhookInfo() {
return this.request('GET', '/v2/webhookcobr');
}
async deleteAutoPixChargeWebhook() {
return this.request('DELETE', '/v2/webhookcobr');
}
}
module.exports = EfipayClient;