@marcelocorrea/mcp-hotmart-api
Version:
MCP Server para integração com API Hotmart
419 lines • 16.7 kB
JavaScript
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