microtecflix/src/services/asaas.ts

366 lines
12 KiB
TypeScript

import { SubscriptionPayment } from '../types';
import { prisma } from '../db/prisma';
export class AsaasService {
public static async getApiKey(): Promise<string | null> {
const db = await prisma.systemSettings.findUnique({ where: { id: 'default' } });
return db?.asaasApiKey || process.env.ASAAS_API_KEY || null;
}
private static async getApiUrl(): Promise<string> {
const db = await prisma.systemSettings.findUnique({ where: { id: 'default' } });
const env = db?.asaasEnvironment || 'sandbox';
return env === 'production' ? 'https://api.asaas.com/v3' : 'https://sandbox.asaas.com/api/v3';
}
private static async getHeaders(): Promise<Record<string, string>> {
const key = await this.getApiKey();
return {
'Content-Type': 'application/json',
'access_token': key || ''
};
}
/**
* Test the connection to Asaas API
*/
public static async testConnection(customApiKey?: string, customEnv?: string): Promise<{ success: boolean; message: string; environment: string }> {
const apiKey = customApiKey || await this.getApiKey();
const db = await prisma.systemSettings.findUnique({ where: { id: 'default' } });
const env = customEnv || db?.asaasEnvironment || 'sandbox';
const apiUrl = env === 'production' ? 'https://api.asaas.com/v3' : 'https://sandbox.asaas.com/v3';
if (!apiKey || apiKey.trim() === '') {
return {
success: false,
message: 'A chave de API (API Key) não foi definida ou está em branco.',
environment: env
};
}
try {
const response = await fetch(`${apiUrl}/customers?limit=1`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'access_token': apiKey.trim()
}
});
if (response.ok) {
return {
success: true,
message: `Conexão estabelecida com sucesso no ambiente ${env.toUpperCase()}!`,
environment: env
};
} else {
const errData = await response.json().catch(() => ({}));
const desc = errData.errors?.[0]?.description || `HTTP ${response.status}`;
return {
success: false,
message: `Falha na autenticação Asaas: ${desc}`,
environment: env
};
}
} catch (err: any) {
return {
success: false,
message: `Erro de conexão de rede: ${err.message || 'Servidor inalcançável'}`,
environment: env
};
}
}
/**
* Create a customer in Asaas
*/
public static async createCustomer(name: string, email: string, cpfCnpj?: string): Promise<string> {
const apiKey = await this.getApiKey();
if (!apiKey) {
console.warn('ASAAS_API_KEY not configured. Simulating customer creation.');
return `cus_sim_${Math.random().toString(36).substring(2, 9)}`;
}
const payload: any = {
name,
email,
notificationDisabled: true
};
if (cpfCnpj && cpfCnpj !== '000.000.000-00') {
payload.cpfCnpj = cpfCnpj.replace(/\D/g, '');
}
try {
const response = await fetch(`${await this.getApiUrl()}/customers`, {
method: 'POST',
headers: await this.getHeaders(),
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const desc = errorData.errors?.[0]?.description || `HTTP ${response.status}`;
throw new Error(`Falha ao criar cliente no Asaas: ${desc}`);
}
const data = await response.json();
return data.id;
} catch (err: any) {
console.error('Asaas createCustomer error:', err);
throw err;
}
}
/**
* Update an existing customer in Asaas
*/
public static async updateCustomer(customerId: string, data: { name?: string, email?: string, cpfCnpj?: string }): Promise<void> {
const apiKey = await this.getApiKey();
if (!apiKey || customerId.startsWith('cus_sim_') || customerId.startsWith('cus_fb_') || customerId === 'cus_test' || ['cus_00001', 'cus_00002', 'cus_00003', 'cus_00004'].includes(customerId)) {
return;
}
const payload: any = {};
if (data.name) payload.name = data.name;
if (data.email) payload.email = data.email;
if (data.cpfCnpj && data.cpfCnpj !== '000.000.000-00') {
payload.cpfCnpj = data.cpfCnpj.replace(/\D/g, '');
}
try {
const response = await fetch(`${await this.getApiUrl()}/customers/${customerId}`, {
method: 'POST',
headers: await this.getHeaders(),
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const desc = errorData.errors?.[0]?.description || `HTTP ${response.status}`;
console.error(`Falha ao atualizar cliente no Asaas: ${desc}`);
}
} catch (err) {
console.error('Asaas updateCustomer error:', err);
}
}
/**
* Create a recurring subscription in Asaas
*/
public static async createSubscription(
customerId: string,
value: number,
billingType: 'PIX' | 'CREDIT_CARD' | 'BOLETO',
creditCardInfo?: any,
cycle: 'MONTHLY' | 'YEARLY' = 'MONTHLY',
description: string = 'Assinatura Mensal DevFlix'
): Promise<{ subscriptionId: string; payment: Partial<SubscriptionPayment> }> {
const apiKey = await this.getApiKey();
if (!apiKey) {
throw new Error('A Chave de API do Asaas (API Key) não foi configurada. Acesse o Painel Admin -> Configurações Globais, cole a sua chave do Asaas Sandbox e clique em Salvar Configurações.');
}
try {
const payload: any = {
customer: customerId,
billingType,
value,
nextDueDate: new Date(Date.now() + 3 * 24 * 3600 * 1000).toISOString().split('T')[0],
cycle,
description
};
if (billingType === 'CREDIT_CARD' && creditCardInfo) {
payload.creditCard = creditCardInfo.creditCard;
payload.creditCardHolderInfo = creditCardInfo.creditCardHolderInfo;
}
const response = await fetch(`${await this.getApiUrl()}/subscriptions`, {
method: 'POST',
headers: await this.getHeaders(),
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.errors?.[0]?.description || 'Failed to create subscription in Asaas');
}
const data = await response.json();
// Retrieve the pending payment/invoice url if possible, or build simulated invoice link
return {
subscriptionId: data.id,
payment: {
id: `pay_asaas_${data.id}`,
value,
status: 'PENDING',
billingType,
invoiceUrl: data.invoiceUrl || `https://sandbox.asaas.com/i/${data.id}`,
dueDate: data.nextDueDate,
paidAt: null,
createdAt: new Date().toISOString()
}
};
} catch (err: any) {
console.error('Asaas createSubscription error:', err);
// Fallback
const subId = `sub_fb_${Math.random().toString(36).substring(2, 9)}`;
return {
subscriptionId: subId,
payment: {
id: `pay_fb_${Math.random().toString(36).substring(2, 9)}`,
value,
status: 'PENDING',
billingType,
invoiceUrl: `https://sandbox.asaas.com/i/fallback_${subId}`,
dueDate: new Date(Date.now() + 3 * 24 * 3600 * 1000).toISOString().split('T')[0],
paidAt: null,
createdAt: new Date().toISOString()
}
};
}
}
/**
* Fetch PIX QR Code for a given payment ID
*/
public static async getPixQrCode(paymentId: string): Promise<{ encodedImage: string, payload: string } | null> {
const apiKey = await this.getApiKey();
const fallbackPayload = `00020126580014br.gov.bcb.pix0136${paymentId}_microtecflix520400005303986540549.905802BR5915MicrotecFlix6009Sao Paulo62070503***6304`;
if (!apiKey || paymentId.startsWith('pay_sim_') || paymentId.startsWith('pay_fb_')) {
return {
encodedImage: '',
payload: fallbackPayload
};
}
try {
const response = await fetch(`${await this.getApiUrl()}/payments/${paymentId}/pixQrCode`, {
method: 'GET',
headers: await this.getHeaders()
});
if (!response.ok) {
return {
encodedImage: '',
payload: fallbackPayload
};
}
const data = await response.json();
return {
encodedImage: data.encodedImage || '',
payload: data.payload || fallbackPayload
};
} catch (err) {
console.error('Asaas getPixQrCode error:', err);
return {
encodedImage: '',
payload: fallbackPayload
};
}
}
/**
* Tokenize a credit card directly with Asaas
*/
public static async tokenizeCreditCard(
customerId: string,
creditCard: any,
creditCardHolderInfo: any
): Promise<{ creditCardToken: string; creditCardNumber: string; creditCardBrand: string }> {
const apiKey = await this.getApiKey();
if (!apiKey) {
return {
creditCardToken: `tok_sim_${Math.random().toString(36).substring(2, 9)}`,
creditCardNumber: creditCard?.number ? creditCard.number.slice(-4) : '4321',
creditCardBrand: 'MASTERCARD'
};
}
try {
const response = await fetch(`${await this.getApiUrl()}/creditCard/tokenize`, {
method: 'POST',
headers: await this.getHeaders(),
body: JSON.stringify({
customer: customerId,
creditCard,
creditCardHolderInfo
})
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.errors?.[0]?.description || 'Falha ao tokenizar cartão no Asaas');
}
return await response.json();
} catch (err: any) {
console.error('Asaas tokenizeCreditCard error:', err);
throw err;
}
}
/**
* Create a single payment in Asaas (supports installments and tokenized card)
*/
public static async createPayment(
customerId: string,
value: number,
billingType: 'PIX' | 'CREDIT_CARD' | 'BOLETO',
description: string,
creditCardInfo?: any,
installmentCount?: number
): Promise<any> {
const apiKey = await this.getApiKey();
const futureDueDate = new Date(Date.now() + 3 * 24 * 3600 * 1000).toISOString().split('T')[0];
if (!apiKey) {
throw new Error('A Chave de API do Asaas (API Key) não foi configurada. Acesse o Painel Admin -> Configurações Globais, cole a sua chave do Asaas Sandbox e clique em Salvar Configurações.');
}
try {
const payload: any = {
customer: customerId,
billingType,
value,
dueDate: futureDueDate,
description
};
if (billingType === 'CREDIT_CARD') {
if (creditCardInfo?.creditCardToken) {
payload.creditCardToken = creditCardInfo.creditCardToken;
} else if (creditCardInfo) {
payload.creditCard = creditCardInfo.creditCard;
payload.creditCardHolderInfo = creditCardInfo.creditCardHolderInfo;
}
if (installmentCount && installmentCount > 1) {
payload.installmentCount = installmentCount;
payload.installmentValue = Number((value / installmentCount).toFixed(2));
}
}
const response = await fetch(`${await this.getApiUrl()}/payments`, {
method: 'POST',
headers: await this.getHeaders(),
body: JSON.stringify(payload)
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.errors?.[0]?.description || 'Falha ao criar cobrança no Asaas');
}
const data = await response.json();
return data;
} catch (err: any) {
console.error('Asaas createPayment error:', err);
throw err;
}
}
}