import { SubscriptionPayment } from '../types'; import { prisma } from '../db/prisma'; export class AsaasService { public static async getApiKey(): Promise { const db = await prisma.systemSettings.findUnique({ where: { id: 'default' } }); return db?.asaasApiKey || process.env.ASAAS_API_KEY || null; } private static async getApiUrl(): Promise { 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> { 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 { 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 { 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 }> { 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 { 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; } } }