306 lines
12 KiB
TypeScript
306 lines
12 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useMemo, useState } from 'react';
|
|
import { initMercadoPago, Payment } from '@mercadopago/sdk-react';
|
|
import { toast } from 'react-hot-toast';
|
|
|
|
interface DadosComprador {
|
|
nome?: string;
|
|
email?: string;
|
|
}
|
|
|
|
interface MercadoPagoPaymentBrickProps {
|
|
pedidoId: string;
|
|
valorTotal: number;
|
|
dadosComprador?: DadosComprador;
|
|
faturaId?: string | null;
|
|
onSuccess?: (payment: any) => void;
|
|
onError?: (error: string) => void;
|
|
onProcessingChange?: (processing: boolean) => void;
|
|
}
|
|
|
|
export const MercadoPagoPaymentBrick = ({
|
|
pedidoId,
|
|
valorTotal,
|
|
dadosComprador,
|
|
faturaId,
|
|
onSuccess,
|
|
onError,
|
|
onProcessingChange
|
|
}: MercadoPagoPaymentBrickProps) => {
|
|
const [brickReady, setBrickReady] = useState(false);
|
|
const [processing, setProcessing] = useState(false);
|
|
|
|
const publicKey = process.env.NEXT_PUBLIC_MERCADO_PAGO_PUBLIC_KEY;
|
|
const amount = useMemo(() => {
|
|
const parsed = Number(valorTotal || 0);
|
|
return Number(parsed.toFixed(2));
|
|
}, [valorTotal]);
|
|
|
|
useEffect(() => {
|
|
if (!publicKey) {
|
|
console.error('❌ NEXT_PUBLIC_MERCADO_PAGO_PUBLIC_KEY não configurada');
|
|
return;
|
|
}
|
|
|
|
// 🔍 Log da chave pública para verificar ambiente (sandbox vs produção)
|
|
const keyPrefix = publicKey.substring(0, 15);
|
|
const isSandbox = publicKey.includes('TEST');
|
|
|
|
initMercadoPago(publicKey, {
|
|
locale: 'pt-BR'
|
|
});
|
|
}, [publicKey]);
|
|
|
|
const enviarPagamento = async (formData: any) => {
|
|
if (!pedidoId) {
|
|
throw new Error('Pedido não encontrado');
|
|
}
|
|
|
|
onProcessingChange?.(true);
|
|
setProcessing(true);
|
|
|
|
const nome = dadosComprador?.nome?.trim() || 'Cliente SaveInMed';
|
|
const [firstName, ...resto] = nome.split(' ');
|
|
const lastName = resto.join(' ') || 'SaveInMed';
|
|
|
|
const payload = {
|
|
...formData,
|
|
transaction_amount: amount,
|
|
pedidoId,
|
|
faturaId: faturaId || undefined,
|
|
description: `Pagamento do pedido ${pedidoId}`,
|
|
payer: {
|
|
...formData.payer,
|
|
email: dadosComprador?.email || formData?.payer?.email,
|
|
first_name: firstName,
|
|
last_name: lastName
|
|
},
|
|
metadata: {
|
|
origem: 'checkout',
|
|
pedidoId
|
|
}
|
|
};
|
|
|
|
const response = await fetch('/api/mercadopago/pagamentos', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(payload)
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
console.log('📡 [API RESPONSE] Status:', response.status);
|
|
console.log('📡 [API RESPONSE] OK?:', response.ok);
|
|
console.log('📡 [API RESPONSE] Body:', result);
|
|
|
|
if (!response.ok || !result.success) {
|
|
console.error('❌ [API ERROR] Detalhes completos:', {
|
|
status: response.status,
|
|
result: result,
|
|
error: result.error,
|
|
details: result.details
|
|
});
|
|
throw new Error(result.error || 'Erro ao criar pagamento');
|
|
}
|
|
|
|
return result.payment;
|
|
};
|
|
|
|
const handleSubmit = ({ formData }: { formData: any }) => {
|
|
return new Promise(async (resolve, reject) => {
|
|
try {
|
|
|
|
// ⚠️ Validação crítica: verificar se token existe para cartões
|
|
if (formData.payment_method_id !== 'pix' && !formData.token) {
|
|
console.error('❌ [PAYMENT BRICK] Token do cartão não foi gerado! formData:', formData);
|
|
throw new Error('Token do cartão não foi gerado. Por favor, verifique os dados do cartão e tente novamente.');
|
|
}
|
|
|
|
const payment = await enviarPagamento(formData);
|
|
const status = payment.status as string;
|
|
|
|
if (status === 'approved') {
|
|
toast.success('Pagamento aprovado com sucesso!');
|
|
} else if (status === 'pending' && payment.payment_method_id === 'pix') {
|
|
toast.success('PIX gerado! Conclua no seu aplicativo bancário.');
|
|
} else {
|
|
toast.success('Pagamento iniciado! Aguarde a confirmação.');
|
|
}
|
|
|
|
onSuccess?.(payment);
|
|
resolve(payment);
|
|
} catch (error) {
|
|
console.error('❌ [PAYMENT BRICK] Erro:', error);
|
|
const message = error instanceof Error ? error.message : 'Erro ao processar pagamento';
|
|
toast.error(message);
|
|
onError?.(message);
|
|
reject(error);
|
|
} finally {
|
|
onProcessingChange?.(false);
|
|
setProcessing(false);
|
|
}
|
|
});
|
|
};
|
|
|
|
if (!publicKey) {
|
|
return (
|
|
<div className="p-4 border border-red-200 bg-red-50 rounded-lg text-sm text-red-700">
|
|
Configuração do Mercado Pago ausente. Defina a variável
|
|
<code className="px-1 mx-1 rounded bg-red-100 text-red-800 text-xs">
|
|
NEXT_PUBLIC_MERCADO_PAGO_PUBLIC_KEY
|
|
</code>
|
|
e tente novamente.
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!pedidoId) {
|
|
return (
|
|
<div className="p-4 border border-yellow-200 bg-yellow-50 rounded-lg text-sm text-yellow-800">
|
|
Aguarde a criação do pedido para liberar o pagamento.
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="w-full">
|
|
{/* Premium Header Card */}
|
|
<div className="bg-gradient-to-br from-blue-600 to-indigo-700 rounded-t-2xl p-6 shadow-lg">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex-1">
|
|
<div className="flex items-center mb-2">
|
|
<div className="bg-white/20 backdrop-blur-sm rounded-lg p-2 mr-3">
|
|
<svg className="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M20 4H4c-1.11 0-1.99.89-1.99 2L2 18c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V6c0-1.11-.89-2-2-2zm0 14H4v-6h16v6zm0-10H4V6h16v2z" />
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-medium text-blue-100">Pagamento Seguro</p>
|
|
<p className="text-xs text-blue-200">Mercado Pago</p>
|
|
</div>
|
|
</div>
|
|
<div className="mt-3">
|
|
<p className="text-3xl font-bold text-white">
|
|
{amount.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' })}
|
|
</p>
|
|
<p className="text-sm text-blue-100 mt-1">Cartão à vista • PIX • Sem taxas extras</p>
|
|
</div>
|
|
</div>
|
|
<div className="text-right">
|
|
<div className={`inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium ${processing ? 'bg-yellow-400 text-yellow-900' :
|
|
brickReady ? 'bg-green-400 text-green-900' :
|
|
'bg-white/20 text-white'
|
|
}`}>
|
|
{processing ? (
|
|
<>
|
|
<svg className="animate-spin -ml-1 mr-2 h-3 w-3" fill="none" viewBox="0 0 24 24">
|
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
|
</svg>
|
|
Processando...
|
|
</>
|
|
) : brickReady ? (
|
|
<>
|
|
<svg className="w-3 h-3 mr-1.5" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
|
</svg>
|
|
Pronto
|
|
</>
|
|
) : (
|
|
'Carregando...'
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Payment Form Container */}
|
|
<div className="bg-white rounded-b-2xl shadow-xl border border-gray-200 border-t-0">
|
|
<div className="p-6">
|
|
{/* Info Banner */}
|
|
<div className="mb-6 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-100 rounded-xl p-4">
|
|
<div className="flex items-start">
|
|
<svg className="w-5 h-5 text-blue-600 mt-0.5 mr-3 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
|
|
</svg>
|
|
<div>
|
|
<p className="text-sm font-medium text-blue-900">Pagamento 100% Seguro</p>
|
|
<p className="text-xs text-blue-700 mt-1">
|
|
Seus dados são protegidos com criptografia de ponta. Não armazenamos informações do cartão.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Payment Brick */}
|
|
<div className="rounded-xl overflow-hidden border border-gray-200 bg-gradient-to-br from-gray-50 to-white p-4 shadow-inner">
|
|
<Payment
|
|
initialization={{
|
|
amount,
|
|
payer: {
|
|
email: dadosComprador?.email || 'cliente@saveinmed.com.br',
|
|
}
|
|
}}
|
|
customization={{
|
|
visual: {
|
|
style: {
|
|
theme: 'default',
|
|
},
|
|
hidePaymentButton: false,
|
|
},
|
|
paymentMethods: {
|
|
creditCard: 'all',
|
|
debitCard: 'all',
|
|
bankTransfer: ['pix'],
|
|
maxInstallments: 1,
|
|
},
|
|
}}
|
|
onSubmit={handleSubmit}
|
|
onError={(error: any) => {
|
|
console.error('❌ [PAYMENT BRICK SDK] Erro ao renderizar/processar:', error);
|
|
console.error('❌ [PAYMENT BRICK SDK] Detalhes do erro:', JSON.stringify(error, null, 2));
|
|
|
|
const errorMessage = error?.message || error?.error || 'Erro ao carregar métodos de pagamento';
|
|
toast.error(errorMessage);
|
|
onError?.(errorMessage);
|
|
}}
|
|
onReady={() => {
|
|
setBrickReady(true);
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
{/* Security Badges */}
|
|
<div className="mt-6 pt-6 border-t border-gray-200">
|
|
<div className="flex items-center justify-center space-x-6 text-xs text-gray-500">
|
|
<div className="flex items-center">
|
|
<svg className="w-4 h-4 text-green-500 mr-1.5" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fillRule="evenodd" d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
|
</svg>
|
|
<span>SSL Seguro</span>
|
|
</div>
|
|
<div className="flex items-center">
|
|
<svg className="w-4 h-4 text-blue-500 mr-1.5" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fillRule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule="evenodd" />
|
|
</svg>
|
|
<span>Dados Criptografados</span>
|
|
</div>
|
|
<div className="flex items-center">
|
|
<svg className="w-4 h-4 text-purple-500 mr-1.5" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fillRule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
|
</svg>
|
|
<span>Certificado PCI</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default MercadoPagoPaymentBrick;
|
|
|