Como integrar doações com Mercado Pago no site do servidor de MU
Integre o Mercado Pago ao site do seu servidor de MU Online para receber doações via Pix e cartão, com preferências de pagamento, webhook de confirmação e entrega automática de créditos de forma segura e idempotente.
Doações são o motor financeiro da maioria dos servidores privados de MU Online, e o Mercado Pago é o gateway mais usado no Brasil porque aceita Pix, cartão e boleto com uma integração relativamente direta. O desafio não é técnico no sentido de complexidade de código, mas de confiabilidade: você prec
Doações são o motor financeiro da maioria dos servidores privados de MU Online, e o Mercado Pago é o gateway mais usado no Brasil porque aceita Pix, cartão e boleto com uma integração relativamente direta. O desafio não é técnico no sentido de complexidade de código, mas de confiabilidade: você precisa garantir que todo pagamento aprovado vire crédito no jogo exatamente uma vez, que nenhum crédito seja liberado sem pagamento real e que falhas de rede ou tentativas de fraude não abram brechas. Neste tutorial você vai montar essa ponte do zero, criando a preferência de pagamento, tratando o retorno do jogador e, o mais importante, processando o webhook que confirma o dinheiro e dispara a entrega automática dos créditos. Detalhes de API e nomes de campos podem mudar; o Mercado Pago atualiza sua documentação com frequência, então trate o código como exemplo funcional a ser conferido contra a referência oficial vigente.
A regra mental que atravessa toda a integração é simples: o site nunca decide sozinho que um pagamento foi feito. Quem decide é o Mercado Pago, e a única forma confiável de saber é consultar a API deles pelo ID do pagamento. O jogador voltando ao site com uma mensagem de sucesso não prova nada, porque essa URL pode ser forjada ou acessada sem pagar. Toda a arquitetura gira em torno de confiar apenas na resposta autenticada da API.
Pré-requisitos
Com o servidor já no ar (se não estiver, veja como criar servidor de MU Online), reúna:
- Conta no Mercado Pago com as credenciais de produção:
Public Keye, principalmente, oAccess Token. - Site com PHP 7.4+ acessível publicamente por HTTPS, pois o webhook precisa de uma URL pública com TLS.
- Extensão cURL habilitada no PHP para chamar a API do Mercado Pago.
- Sistema de contas e login funcionando, para saber a qual conta creditar.
- Tabela de saldo/créditos já definida no banco (WCoin, pontos de doação ou o que seu servidor usa).
- Tabela de controle de transações com chave de idempotência, como a modelada no tutorial de Cash Shop.
- Um certificado SSL válido no domínio; o Mercado Pago não entrega webhook para endpoints sem HTTPS confiável.
Guarde o Access Token com o mesmo cuidado de uma senha de banco. Ele permite movimentar sua conta pela API. Nunca o coloque em código versionado público, nunca o exponha no front-end e mantenha-o em um arquivo de configuração fora do webroot.
Como funciona o fluxo de doação
Entender o ciclo completo antes de codar evita erros de arquitetura. O fluxo tem cinco etapas bem definidas, e cada uma tem um dono claro.
| Etapa | Onde acontece | Responsável | Confiável para creditar? |
|---|---|---|---|
| Jogador escolhe pacote | Site | Seu PHP | Não |
| Criação da preferência | Site chama API MP | Seu PHP + API | Não |
| Pagamento | Tela do Mercado Pago | Mercado Pago | Não |
| Retorno do jogador | back_url no site | Navegador | Não |
| Notificação webhook | API MP chama seu endpoint | Mercado Pago | Sim, após consultar API |
Repare que só a última linha é confiável para liberar crédito. Todas as outras são partes do fluxo de experiência, mas nenhuma prova pagamento. Essa tabela é o mapa mental que impede o erro clássico de creditar na back_url.
Guardando as credenciais com segurança
Isole as credenciais em um arquivo de configuração que fica fora da pasta pública e nunca vai para o controle de versão.
<?php
// config/mercadopago.php (fora do webroot ou protegido)
return [
'access_token' => getenv('MP_ACCESS_TOKEN') ?: 'APP_USR-xxxxxxxx',
'public_key' => getenv('MP_PUBLIC_KEY') ?: 'APP_USR-yyyyyyyy',
'base_url' => 'https://api.mercadopago.com',
'site_url' => 'https://seuservidor.com',
];
Preferir getenv() permite injetar as credenciais por variável de ambiente em produção, o que é a prática recomendada. O valor fixo serve só de fallback para desenvolvimento. Em produção, defina MP_ACCESS_TOKEN no ambiente do servidor web e deixe o código sem o segredo escrito.
Criando a preferência de pagamento
A preferência é o objeto que descreve a doação: valor, descrição, URLs de retorno e a URL de notificação. Você a cria chamando a API do Mercado Pago, que devolve um link para o qual o jogador é enviado.
<?php
// criar-doacao.php
session_start();
$cfg = require __DIR__ . '/config/mercadopago.php';
if (empty($_SESSION['conta_mu'])) {
header('Location: /login');
exit;
}
$pacotes = [
'p1' => ['titulo' => 'Doação 1000 WCoin', 'valor' => 10.00, 'wcoin' => 1000],
'p2' => ['titulo' => 'Doação 2500 WCoin', 'valor' => 20.00, 'wcoin' => 2500],
'p3' => ['titulo' => 'Doação 6000 WCoin', 'valor' => 40.00, 'wcoin' => 6000],
];
$id = $_GET['pacote'] ?? '';
if (!isset($pacotes[$id])) { http_response_code(400); exit('Pacote inválido'); }
$p = $pacotes[$id];
// external_reference amarra o pagamento à conta e ao pacote no nosso banco
$refExterna = json_encode([
'conta' => $_SESSION['conta_mu'],
'pacote' => $id,
'wcoin' => $p['wcoin'],
'nonce' => bin2hex(random_bytes(8)),
]);
$preferencia = [
'items' => [[
'title' => $p['titulo'],
'quantity' => 1,
'unit_price' => $p['valor'],
'currency_id' => 'BRL',
]],
'external_reference' => $refExterna,
'back_urls' => [
'success' => $cfg['site_url'] . '/doacao-retorno.php?st=ok',
'pending' => $cfg['site_url'] . '/doacao-retorno.php?st=pend',
'failure' => $cfg['site_url'] . '/doacao-retorno.php?st=fail',
],
'auto_return' => 'approved',
'notification_url' => $cfg['site_url'] . '/webhook-mercadopago.php',
];
$ch = curl_init($cfg['base_url'] . '/checkout/preferences');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $cfg['access_token'],
],
CURLOPT_POSTFIELDS => json_encode($preferencia),
]);
$resposta = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http !== 201) {
error_log('Falha ao criar preferência MP: ' . $resposta);
exit('Erro ao iniciar o pagamento. Tente novamente.');
}
$dados = json_decode($resposta, true);
// Redireciona o jogador para o checkout do Mercado Pago
header('Location: ' . $dados['init_point']);
exit;
O campo external_reference é a cola entre o Mercado Pago e o seu banco. Nele você guarda a conta, o pacote e a quantidade de WCoin, além de um nonce aleatório para tornar cada preferência única. Quando o webhook chegar, você lê essa referência de volta e sabe exatamente a quem creditar e quanto, sem depender de sessão ou cookie, que não existem no contexto do webhook.
O notification_url é o endereço que o Mercado Pago vai chamar quando o status do pagamento mudar. É esse endpoint que faz o trabalho de verdade.
A página de retorno do jogador
Quando o pagamento termina, o Mercado Pago redireciona o jogador para uma das back_urls. Essa página só serve para dar feedback visual. Ela não credita nada.
<?php
// doacao-retorno.php
$status = $_GET['st'] ?? '';
$mensagens = [
'ok' => 'Pagamento recebido! Seus créditos serão liberados em instantes.',
'pend' => 'Pagamento pendente. Assim que for aprovado, os créditos entram.',
'fail' => 'O pagamento não foi concluído. Nenhum valor foi cobrado.',
];
$msg = $mensagens[$status] ?? 'Status desconhecido.';
?>
<h1>Doação</h1>
<p><?= htmlspecialchars($msg) ?></p>
<p><a href="/painel">Voltar ao painel</a></p>
Note o texto "serão liberados em instantes" mesmo no sucesso. Isso é proposital: a liberação depende do webhook, que normalmente chega em segundos, mas é assíncrono. Prometer liberação imediata nessa página seria mentir, porque o crédito ainda não aconteceu. Gerenciar essa expectativa reduz reclamações de "paguei e não recebi na hora".
O webhook: onde o crédito realmente acontece
Este é o endpoint mais importante de toda a integração. Ele recebe a notificação, consulta a API para confirmar o status verdadeiro e, só então, credita de forma idempotente.
<?php
// webhook-mercadopago.php
$cfg = require __DIR__ . '/config/mercadopago.php';
require_once __DIR__ . '/lib/cashshop.php'; // creditarWCoin() com idempotência
// 1) Responder rápido é importante; capturamos o ID do pagamento
$entrada = json_decode(file_get_contents('php://input'), true);
$tipo = $_GET['type'] ?? $entrada['type'] ?? '';
$pagamentoId = $_GET['data.id'] ?? $entrada['data']['id'] ?? null;
if ($tipo !== 'payment' || !$pagamentoId) {
http_response_code(200); // reconhece e ignora o que não interessa
exit;
}
// 2) NUNCA confie no corpo. Consulte a API pelo ID.
$ch = curl_init($cfg['base_url'] . "/v1/payments/{$pagamentoId}");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $cfg['access_token']],
]);
$resp = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http !== 200) {
http_response_code(500); // MP tentará de novo depois
exit;
}
$pag = json_decode($resp, true);
// 3) Só aprova credita
if (($pag['status'] ?? '') !== 'approved') {
http_response_code(200);
exit;
}
// 4) Ler o external_reference que amarramos na criação
$ref = json_decode($pag['external_reference'] ?? '{}', true);
$conta = $ref['conta'] ?? null;
$wcoin = (int)($ref['wcoin'] ?? 0);
if (!$conta || $wcoin <= 0) {
http_response_code(200); // nada a fazer
exit;
}
// 5) Creditar usando o ID do pagamento como chave de idempotência
$ok = creditarWCoin($conta, $wcoin, 'mercadopago', (string)$pagamentoId);
http_response_code($ok ? 200 : 500);
Três decisões definem a segurança deste webhook. Primeiro, ele ignora completamente qualquer valor de status vindo no corpo da notificação e vai buscar a verdade na API com o Access Token, o que torna impossível forjar um pagamento aprovado. Segundo, ele usa o external_reference gravado na criação para saber a quem creditar, sem depender de sessão. Terceiro, ele passa o $pagamentoId como chave de idempotência para a função creditarWCoin, que, com o índice único no banco, garante que webhooks repetidos, algo normal no Mercado Pago, nunca creditem em dobro.
O código de resposta HTTP também é estratégico. Retornar 200 diz ao Mercado Pago "recebi, pode parar de reenviar". Retornar 500 diz "deu problema, tente de novo mais tarde". Por isso, quando a consulta à API falha por instabilidade momentânea, respondemos 500 de propósito: queremos que o Mercado Pago reenvie a notificação até conseguirmos processar.
A função de crédito idempotente
O webhook depende de uma função de crédito que seja segura contra duplicidade. Ela reaproveita exatamente a mesma lógica transacional do sistema de Cash Shop.
<?php
// lib/cashshop.php (trecho essencial)
function creditarWCoin(string $conta, int $qtd, string $origem, string $ref): bool {
if ($qtd <= 0) return false;
$conn = getDB();
// A procedure faz: valida conta, checa GatewayRef (idempotência),
// soma WCoin em transação e grava o log. Ver tutorial de Cash Shop.
$stmt = sqlsrv_query($conn, "{CALL dbo.CreditarWCoin(?, ?, ?, ?)}", [
[$conta, SQLSRV_PARAM_IN],
[$qtd, SQLSRV_PARAM_IN],
[$origem, SQLSRV_PARAM_IN],
[$ref, SQLSRV_PARAM_IN],
]);
if ($stmt === false) {
error_log('Erro crédito doação: ' . print_r(sqlsrv_errors(), true));
return false;
}
return true;
}
A $ref aqui é o ID do pagamento do Mercado Pago. Como a procedure grava esse valor em uma coluna com índice único, uma segunda tentativa de creditar o mesmo pagamento é barrada pelo próprio banco. Essa é a diferença entre um sistema que aguenta reenvios do gateway e um que credita créditos fantasmas.
Testando antes de ir para produção
Nunca coloque um sistema de pagamento no ar sem testar o fluxo completo. O Mercado Pago fornece credenciais de teste e usuários de teste que simulam pagador e vendedor. Com eles você percorre todo o caminho, do clique ao crédito, sem mover dinheiro real.
- Gere as credenciais de teste no painel de desenvolvedor do Mercado Pago.
- Crie usuários de teste comprador e vendedor.
- Use o
Access Tokende teste na configuração. - Faça uma doação com um cartão de teste aprovado e confirme que o webhook credita.
- Faça uma doação com cartão recusado e confirme que nada é creditado.
- Simule um reenvio do webhook e confirme que o crédito não duplica.
- Só depois troque para as credenciais de produção.
Testar o caso do webhook duplicado é o passo que mais gente pula e o que mais causa prejuízo depois. Force-o de propósito antes de confiar no sistema.
Segurança e conformidade
Pagamento envolve dinheiro e dados sensíveis, então algumas regras não são negociáveis. Nunca guarde dados de cartão no seu banco; deixe isso inteiramente com o Mercado Pago usando o Checkout Pro. Mantenha o Access Token fora do código público e do front-end. Sirva todo o fluxo sob HTTPS, incluindo o endpoint de webhook. Registre cada notificação recebida em log para auditoria, mesmo as ignoradas, porque isso ajuda a investigar disputas. E aplique limites de sanidade: se chegar um pagamento com valor que não corresponde a nenhum pacote conhecido, registre e não credite automaticamente, tratando como caso a revisar manualmente.
Vale reforçar que este tutorial cobre a integração técnica, não orientação financeira, contábil ou fiscal. Regras de tributação sobre doações e movimentação de conta são responsabilidade sua e do seu contador; consulte um profissional para essa parte.
Erros comuns e soluções
| Sintoma | Causa provável | Solução |
|---|---|---|
| Crédito liberado sem pagamento | Crédito feito na back_url | Mova o crédito exclusivamente para o webhook |
| Webhook nunca chega | notification_url sem HTTPS válido | Configure SSL confiável e URL pública correta |
| Jogador pagou e não recebeu | Webhook falhou e retornou 200 | Retorne 500 em falha para forçar reenvio; verifique logs |
| Crédito em dobro | Reenvio do webhook sem idempotência | Use o ID do pagamento como chave única no banco |
| Não sei a qual conta creditar | external_reference não preenchido | Grave conta e pacote no external_reference ao criar a preferência |
| Notificação forjada creditou | Confiou no corpo sem consultar API | Sempre consulte /v1/payments/{id} com o Access Token |
| Erro 401 na API | Access Token errado ou de teste em produção | Confirme o token correto do ambiente vigente |
| Valor divergente do pacote | Manipulação ou pacote alterado | Valide o valor pago contra a tabela de pacotes |
Checklist de lançamento
- Access Token de produção guardado fora do webroot e do versionamento
- Site inteiro servido sob HTTPS com certificado válido
- Preferência de pagamento criada com external_reference contendo conta e wcoin
- notification_url apontando para o webhook público e correto
- Webhook consultando /v1/payments/{id} em vez de confiar no corpo
- Crédito acontecendo somente quando status é approved
- ID do pagamento usado como chave de idempotência no crédito
- Índice único na coluna de referência de transação no banco
- Página de retorno apenas informativa, sem creditar
- Códigos HTTP 200/500 usados corretamente para controlar reenvios
- Fluxo completo testado com credenciais e usuários de teste
- Teste de webhook duplicado confirmando crédito único
- Teste de pagamento recusado confirmando ausência de crédito
- Log de todas as notificações recebidas ativo
- Limites de sanidade de valor por transação configurados
Com esse fluxo, seu servidor recebe doações via Pix e cartão de forma automática, segura e à prova dos reenvios que fazem parte da vida real de qualquer gateway. O princípio que sustenta tudo se repete de ponta a ponta: o crédito só nasce de um pagamento que a própria API do Mercado Pago confirmou como aprovado, entregue exatamente uma vez.
Perguntas frequentes
Preciso de CNPJ para usar o Mercado Pago no servidor?
Não obrigatoriamente; a conta pode ser pessoa física. Porém, para volumes maiores e para reduzir riscos de bloqueio por movimentação atípica, muitos donos de servidor abrem MEI ou conta empresarial. As regras de conta e limites variam e são definidas pelo Mercado Pago, não pelo código.
Qual a diferença entre Checkout Pro e Checkout Transparente?
No Checkout Pro o jogador é redirecionado para a tela do Mercado Pago, que cuida de tudo, o que é mais simples e seguro para começar. No Checkout Transparente o pagamento acontece dentro do seu site, exigindo mais responsabilidade com dados. Para servidores de MU, o Checkout Pro costuma ser o caminho recomendado.
O crédito deve ser entregue na página de retorno ou no webhook?
Sempre no webhook. A página de retorno (back_url) serve apenas para mostrar uma mensagem ao jogador e pode ser fechada, recarregada ou nem acessada. A entrega do crédito tem que acontecer na notificação webhook, que é a fonte confiável do status real do pagamento.
Como confirmo que a notificação veio mesmo do Mercado Pago?
Nunca confie no corpo da notificação diretamente. Ao receber o webhook, consulte a API do Mercado Pago pelo ID do pagamento usando seu Access Token e verifique o status retornado pela própria API. Opcionalmente, valide a assinatura do cabeçalho quando disponível.
Pix cai na hora no crédito do jogador?
O Pix confirma em segundos, mas o crédito no jogo só entra quando o webhook chega e você consulta a API confirmando o status approved. Na prática são poucos segundos, mas nunca credite antes dessa confirmação, mesmo que o jogador jure que pagou.