397 lines
No EOL
11 KiB
TypeScript
397 lines
No EOL
11 KiB
TypeScript
/**
|
||
* 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
|
||
};
|
||
}
|
||
}; |