saveinmed/frontend/src/services/mercadoPagoService.ts

397 lines
No EOL
11 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Serviço para integração com Mercado Pago via API principal
* Baseado na API: https://bff-dev.saveinmed.com.br/mercadopago/preference
*/
import { API_ROOT_URL, API_V1_BASE_URL } from '@/lib/apiBase';
const API_BASE_URL = API_V1_BASE_URL;
const WEBHOOK_BASE = API_ROOT_URL;
const FRONT_URL = process.env.NEXT_PUBLIC_FRONTEND_URL;
/**
* Interface para item do Mercado Pago
*/
export interface MercadoPagoItem {
currency_id: string;
id: string;
quantity: number;
title: string;
unit_price: number;
}
/**
* Interface para dados do pagador
*/
export interface MercadoPagoPayer {
email: string;
name: string;
surname: string;
}
/**
* Interface para URLs de retorno
*/
export interface MercadoPagoBackUrls {
failure: string;
success: string;
pending?: string;
}
/**
* Interface para configurações de pagamento
*/
export interface MercadoPagoPaymentMethods {
excluded_payment_methods?: Array<{ id: string }>;
excluded_payment_types?: Array<{ id: string }>;
installments?: number;
default_installments?: number;
}
/**
* Interface para dados da preferência do Mercado Pago
*/
export interface MercadoPagoPreferenceData {
back_urls: MercadoPagoBackUrls;
external_reference: string;
items: MercadoPagoItem[];
notification_url: string;
payer: MercadoPagoPayer;
payment_methods?: MercadoPagoPaymentMethods;
}
/**
* Interface para resposta da API
*/
export interface MercadoPagoResponse {
success: boolean;
data?: any;
error?: string;
message?: string;
preference_id?: string;
init_point?: string;
sandbox_init_point?: string;
}
/**
* Serviço do Mercado Pago
*/
export const mercadoPagoService = {
/**
* Obtém o token de autenticação do localStorage
*/
getAuthToken: (): string | null => {
return localStorage.getItem('access_token');
},
/**
* Obtém dados do usuário atual
*/
getUserData: (): any => {
try {
const userStr = localStorage.getItem('user');
if (!userStr) return null;
return JSON.parse(userStr);
} catch (error) {
console.error('Erro ao obter dados do usuário:', error);
return null;
}
},
/**
* Cria uma preferência de pagamento no Mercado Pago
*/
criarPreferencia: async (
pedidoId: string,
itens: Array<{
produto: {
id: string;
nome?: string;
descricao?: string;
preco_venda?: number;
preco_base?: number;
preco_final?: number;
};
quantidade: number;
}>,
dadosComprador?: {
nome?: string;
email?: string;
}
): Promise<MercadoPagoResponse> => {
try {
const token = mercadoPagoService.getAuthToken();
const userData = mercadoPagoService.getUserData();
// Mapear itens para formato do Mercado Pago
const mercadoPagoItems: MercadoPagoItem[] = itens.map((item, index) => {
const preco = (item.produto as any).preco_final || 0;
const nome = item.produto.nome || item.produto.descricao || `Produto ${index + 1}`;
return {
currency_id: "BRL",
id: item.produto.id || `item-${index}`,
quantity: item.quantidade,
title: nome.substring(0, 255), // Mercado Pago tem limite de caracteres
unit_price: preco
};
});
// Dados do comprador
const nomeCompleto = dadosComprador?.nome || userData?.nome || userData?.name || 'Cliente';
const email = dadosComprador?.email || userData?.email || 'cliente@example.com';
// Separar nome e sobrenome
const partesNome = nomeCompleto.split(' ');
const nome = partesNome[0] || 'Cliente';
const sobrenome = partesNome.slice(1).join(' ') || 'Teste';
// Montar dados da preferência
const preferenceData: MercadoPagoPreferenceData = {
back_urls: {
failure: `${FRONT_URL}/pagamento/erro`,
success: `${FRONT_URL}/pagamento/sucesso`,
pending: `${FRONT_URL}/pagamento/mercadopago/pendente?ref=${pedidoId}`
},
external_reference: pedidoId,
items: mercadoPagoItems,
notification_url: `${WEBHOOK_BASE}/mercadopago/webhook`,
payer: {
email: email,
name: nome,
surname: sobrenome
},
payment_methods: {
installments: 1, // Máximo de 1 parcela (à vista)
default_installments: 1, // Padrão 1 parcela (à vista)
excluded_payment_types: [ // Excluir boletos
{ id: "ticket" } // Remove boletos bancários
]
}
};
// Headers para a requisição
const headers: HeadersInit = {
'accept': 'application/json',
'Content-Type': 'application/json',
};
// Adicionar token se disponível
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(`${API_ROOT_URL}/mercadopago/preference`, {
method: 'POST',
headers,
body: JSON.stringify(preferenceData),
});
const data = await response.json();
if (response.ok) {
return {
success: true,
data,
preference_id: data.id,
init_point: data.init_point,
sandbox_init_point: data.sandbox_init_point
};
} else {
console.error('❌ Erro ao criar preferência Mercado Pago:', data);
return {
success: false,
error: data.message || data.error || 'Erro ao criar preferência de pagamento'
};
}
} catch (error) {
console.error('💥 Erro na criação da preferência Mercado Pago:', error);
return {
success: false,
error: 'Erro de conexão ao criar preferência de pagamento'
};
}
},
/**
* Verifica status de um pagamento
*/
verificarStatusPagamento: async (paymentId: string): Promise<MercadoPagoResponse> => {
try {
const response = await fetch(`/api/mercadopago/verificar-pagamento?id=${paymentId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (response.ok && data.success) {
return { success: true, data: data.data };
} else {
console.error('❌ Erro ao verificar status:', data);
return {
success: false,
error: data.error || 'Erro ao verificar status do pagamento'
};
}
} catch (error) {
console.error('💥 Erro ao verificar status:', error);
return {
success: false,
error: 'Erro de conexão ao verificar status'
};
}
},
/**
* Verifica status de um pagamento por external_reference
*/
verificarStatusPorReferencia: async (externalReference: string): Promise<MercadoPagoResponse> => {
try {
const response = await fetch(`/api/mercadopago/verificar-pagamento?external_reference=${externalReference}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (response.ok && data.success) {
return { success: true, data: data.data };
} else {
console.error('❌ Erro ao verificar status por referência:', data);
return {
success: false,
error: data.error || 'Erro ao verificar status do pagamento'
};
}
} catch (error) {
console.error('💥 Erro ao verificar status por referência:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Erro de conexão ao verificar status'
};
}
},
/**
* Processa notificação de webhook do Mercado Pago
*/
processarNotificacao: async (notificationData: any): Promise<MercadoPagoResponse> => {
try {
const token = mercadoPagoService.getAuthToken();
const headers: HeadersInit = {
'accept': 'application/json',
'Content-Type': 'application/json',
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(`${API_BASE_URL}/mercadopago/webhook`, {
method: 'POST',
headers,
body: JSON.stringify(notificationData),
});
const data = await response.json();
if (response.ok) {
return { success: true, data };
} else {
console.error('❌ Erro ao processar notificação:', data);
return {
success: false,
error: data.message || 'Erro ao processar notificação'
};
}
} catch (error) {
console.error('💥 Erro ao processar notificação:', error);
return {
success: false,
error: 'Erro de conexão ao processar notificação'
};
}
},
/**
* Calcula valor total dos itens
*/
calcularValorTotal: (itens: Array<{
produto: {
preco_venda?: number;
preco_base?: number;
preco_final?: number;
};
quantidade: number;
}>): number => {
return itens.reduce((total, item) => {
const preco = (item.produto as any).preco_final || 0;
return total + (preco * item.quantidade);
}, 0);
},
/**
* Formata preço para exibição
*/
formatarPreco: (valor: number): string => {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL'
}).format(valor);
},
/**
* Valida dados de preferência antes de enviar
*/
validarDadosPreferencia: (
pedidoId: string,
itens: Array<any>
): { valido: boolean; erros: string[] } => {
const erros: string[] = [];
// Validar pedido ID
if (!pedidoId || pedidoId.trim() === '') {
erros.push('ID do pedido é obrigatório');
}
// Validar itens
if (!itens || itens.length === 0) {
erros.push('Pelo menos um item é obrigatório');
} else {
itens.forEach((item, index) => {
if (!item.produto) {
erros.push(`Item ${index + 1}: dados do produto são obrigatórios`);
} else {
if (!item.produto.id) {
erros.push(`Item ${index + 1}: ID do produto é obrigatório`);
}
const preco = (item.produto as any).preco_final;
if (!preco || preco <= 0) {
erros.push(`Item ${index + 1}: preço final deve ser maior que zero`);
}
if (!item.quantidade || item.quantidade <= 0) {
erros.push(`Item ${index + 1}: quantidade deve ser maior que zero`);
}
}
});
}
return {
valido: erros.length === 0,
erros
};
}
};