UNPKG

@marcelocorrea/mcp-hotmart-api

Version:

MCP Server para integração com API Hotmart

419 lines 16.7 kB
import dotenv from 'dotenv'; dotenv.config(); import { z } from 'zod'; // ===== SCHEMAS DE VALIDAÇÃO HOTMART ===== export const HotmartProductSchema = z.object({ id: z.number(), name: z.string(), ucode: z.string(), status: z.enum(['ACTIVE', 'INACTIVE', 'PAUSED', 'DRAFT', 'CHANGES_PENDING_ON_PRODUCT']), price: z.number().optional(), currency: z.string().optional(), commission_percentage: z.number().optional(), created_at: z.union([z.string(), z.number()]).optional(), updated_at: z.union([z.string(), z.number()]).optional(), warranty_period: z.number().optional(), is_subscription: z.boolean().optional(), format: z.string().optional(), }).passthrough(); export const HotmartOfferSchema = z.object({ code: z.string().optional(), name: z.string().optional(), price: z .object({ value: z.number().optional(), currency_code: z.string().optional(), }) .passthrough() .optional(), status: z.string().optional(), }).passthrough(); export const HotmartSaleSchema = z.object({ product: z.object({ id: z.number(), name: z.string(), ucode: z.string().optional(), }).passthrough(), buyer: z.object({ name: z.string().optional(), email: z.string().email(), ucode: z.string().optional(), document: z.string().optional(), }).passthrough(), purchase: z.object({ transaction: z.string(), price: z.object({ value: z.number(), currency_code: z.string().optional(), }).passthrough(), payment: z.object({ method: z.string().optional(), type: z.string().optional(), installments_number: z.number().optional(), }).passthrough().optional(), approved_date: z.union([z.string(), z.number(), z.null()]).optional(), status: z.string().optional(), order_date: z.union([z.string(), z.number()]).optional(), warranty_expire_date: z.union([z.string(), z.number()]).optional(), is_subscription: z.boolean().optional(), recurrency_number: z.number().optional(), hotmart_fee: z.object({ total: z.number().optional(), fixed: z.number().optional(), currency_code: z.string().optional(), base: z.number().optional(), percentage: z.number().optional(), }).passthrough().optional(), commission_as: z.string().optional(), offer: z.object({ payment_mode: z.string().optional(), code: z.string().optional(), }).passthrough().optional(), }).passthrough(), producer: z.object({ ucode: z.string().optional(), name: z.string().optional(), }).passthrough().optional(), }).passthrough(); export const HotmartSalesResponseSchema = z.object({ items: z.array(HotmartSaleSchema), page_info: z.object({ number: z.number(), size: z.number(), total_elements: z.number(), total_pages: z.number(), }).passthrough(), }).passthrough(); // ===== CLIENTE HOTMART API ===== export class HotmartClient { baseUrl = 'https://api-sec-vlc.hotmart.com'; apiUrl = 'https://developers.hotmart.com'; clientId; clientSecret; accessToken = null; tokenExpiry = null; constructor() { this.clientId = process.env.HOTMART_CLIENT_ID; this.clientSecret = process.env.HOTMART_CLIENT_SECRET; if (!this.clientId || !this.clientSecret) { throw new Error('Credenciais Hotmart (CLIENT_ID e CLIENT_SECRET) não configuradas no .env'); } console.error('🔧 Cliente Hotmart MCP inicializado'); } async authenticate() { try { console.error('🔐 Autenticando com Hotmart API...'); const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64'); const url = `${this.baseUrl}/security/oauth/token`; const headers = { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': `Basic ${credentials}`, }; const body = new URLSearchParams({ grant_type: 'client_credentials', }); const response = await fetch(url, { method: 'POST', headers, body, }); const responseText = await response.text(); if (!response.ok) { throw new Error(`Erro na autenticação Hotmart: ${response.status} - ${responseText}`); } if (!responseText.trim()) { throw new Error('Resposta vazia da API Hotmart'); } const data = JSON.parse(responseText); if (!data.access_token) { throw new Error('Token de acesso não retornado pela API'); } this.accessToken = data.access_token; this.tokenExpiry = new Date(Date.now() + (data.expires_in - 300) * 1000); console.error('✅ Autenticação Hotmart realizada com sucesso'); return this.accessToken; } catch (error) { console.error('❌ Erro na autenticação Hotmart:', error); throw error; } } async getValidToken() { if (!this.accessToken || !this.tokenExpiry || new Date() >= this.tokenExpiry) { await this.authenticate(); } if (!this.accessToken) { throw new Error('Falha na autenticação - token não obtido'); } return this.accessToken; } async getProducts() { const token = await this.getValidToken(); try { console.error('🔍 Buscando produtos Hotmart...'); const response = await fetch(`${this.apiUrl}/products/api/v1/products`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Erro ao buscar produtos: ${response.status} ${response.statusText}`); } const data = (await response.json()); const items = data.items || data || []; const validatedProducts = items.map((item) => { try { return HotmartProductSchema.parse(item); } catch (error) { console.error('⚠️ Produto com formato inválido ignorado:', item); return null; } }).filter(Boolean); console.error(`✅ ${validatedProducts.length} produtos válidos encontrados`); return validatedProducts; } catch (error) { console.error('❌ Erro ao buscar produtos:', error); throw error; } } async getProductOffers(productUcode) { if (!productUcode || !productUcode.trim()) { throw new Error('O código do produto (ucode) é obrigatório'); } const token = await this.getValidToken(); try { console.error(`🔍 Buscando ofertas para o produto ${productUcode}...`); const response = await fetch(`${this.apiUrl}/products/api/v1/products/${encodeURIComponent(productUcode)}/offers`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, }); const responseText = await response.text(); if (!response.ok) { throw new Error(`Erro ao buscar ofertas: ${response.status} ${response.statusText} - ${responseText}`); } if (!responseText.trim()) { console.error('⚠️ Resposta vazia ao buscar ofertas'); return []; } const data = JSON.parse(responseText); const rawItems = Array.isArray(data) ? data : Array.isArray(data?.items) ? data.items : []; const validatedOffers = rawItems .map((item) => { try { return HotmartOfferSchema.parse(item); } catch (error) { console.error('⚠️ Oferta com formato inválido ignorada:', item); return null; } }) .filter(Boolean); console.error(`✅ ${validatedOffers.length} ofertas encontradas para ${productUcode}`); return validatedOffers; } catch (error) { console.error('❌ Erro ao buscar ofertas:', error); throw error; } } async getSubscribers(productId) { try { console.error('🔍 Buscando assinaturas Hotmart...'); const token = await this.getValidToken(); const params = new URLSearchParams(); if (productId) { params.append('product_id', productId); console.error(`🎯 Filtrando assinaturas por produto: ${productId}`); } const url = `${this.apiUrl}/payments/api/v1/subscriptions${params.toString() ? '?' + params.toString() : ''}`; const response = await fetch(url, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Erro ${response.status}: ${errorText}`); } const data = (await response.json()); let allSubscriptions = []; if (Array.isArray(data)) { allSubscriptions = data; } else if (data.items && Array.isArray(data.items)) { allSubscriptions = data.items; } else if (data.content && Array.isArray(data.content)) { allSubscriptions = data.content; } else if (data.data && Array.isArray(data.data)) { allSubscriptions = data.data; } console.error(`✅ ${allSubscriptions.length} assinaturas encontradas`); return allSubscriptions; } catch (error) { console.error('❌ Erro ao buscar assinaturas:', error); return []; } } async getSales(dateParams) { const token = await this.getValidToken(); try { console.error('🔍 Buscando vendas Hotmart...'); let startDate; let endDate; if (dateParams?.startDate && dateParams?.endDate) { startDate = dateParams.startDate; endDate = dateParams.endDate; } else { startDate = new Date('2023-01-01').getTime(); endDate = new Date().getTime(); } let allSales = []; let pageToken = ''; let hasMorePages = true; const maxResults = 200; let pageCount = 1; const maxPages = 50; while (hasMorePages && pageCount <= maxPages) { const params = new URLSearchParams({ max_results: maxResults.toString(), start_date: startDate.toString(), end_date: endDate.toString(), }); if (dateParams?.productId) { params.append('product_id', dateParams.productId); } if (pageToken) { params.append('page_token', pageToken); } const url = `${this.apiUrl}/payments/api/v1/sales/history?${params}`; const response = await fetch(url, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, }); if (!response.ok) { const errorText = await response.text(); console.error(`❌ Erro página ${pageCount}:`, response.status, errorText); break; } const data = (await response.json()); if (data.items && Array.isArray(data.items)) { const validatedSales = data.items.map((item) => { try { return HotmartSaleSchema.parse(item); } catch (error) { console.error('⚠️ Venda com formato inválido ignorada:', item); return null; } }).filter(Boolean); allSales.push(...validatedSales); } if (data.page_info && data.page_info.next_page_token) { pageToken = data.page_info.next_page_token; pageCount++; } else { hasMorePages = false; } if (hasMorePages) { await new Promise(resolve => setTimeout(resolve, 100)); } } console.error(`✅ Total de vendas: ${allSales.length}`); return allSales; } catch (error) { console.error('❌ Erro ao buscar vendas:', error); return []; } } async getSalesByTransaction(transactionId) { try { console.error(`🎯 Buscando transação: ${transactionId}`); const periodsInDays = [7, 30, 90, 180, 365]; for (const days of periodsInDays) { const endDate = new Date(); const startDate = new Date(); startDate.setDate(startDate.getDate() - days); const sales = await this.getSales({ startDate: startDate.getTime(), endDate: endDate.getTime() }); const specificSale = sales.find(sale => sale.purchase.transaction === transactionId); if (specificSale) { console.error(`✅ Transação ${transactionId} encontrada!`); return specificSale; } } console.error(`❌ Transação ${transactionId} não encontrada`); return null; } catch (error) { console.error(`❌ Erro ao buscar transação ${transactionId}:`, error); throw error; } } async getSubscription(subscriberCode) { try { const token = await this.getValidToken(); console.error(`🔍 Buscando assinatura: ${subscriberCode}`); const response = await fetch(`${this.apiUrl}/payments/rest/v2/subscriptions/${subscriberCode}`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Erro ao buscar assinatura: ${response.status} - ${errorText}`); } const data = (await response.json()); console.error(`✅ Assinatura encontrada: ${subscriberCode}`); return data; } catch (error) { console.error('❌ Erro ao buscar assinatura Hotmart:', error); throw error; } } async testConnection() { try { console.error('🧪 Testando conexão com Hotmart API...'); const token = await this.getValidToken(); if (token && token.length > 0) { console.error('✅ Conexão com Hotmart API funcionando!'); return true; } else { console.error('❌ Token inválido recebido'); return false; } } catch (error) { console.error('❌ Falha na conexão com Hotmart API:', error); return false; } } } //# sourceMappingURL=hotmart-client.js.map