Como configurar o Cash Shop (loja virtual) do servidor de MU
Configure o Cash Shop no servidor de MU Online — WCoin shop do GameServer, loja integrada ao site, sistema de créditos, tabelas SQL, entrega automática de itens e gateway de pagamento.
O Cash Shop (loja virtual) é a principal fonte de receita de servidores privados de MU Online. A implementação mais comum usa um sistema web com créditos (WCoins, Cash Points ou similar) que são comprados por doação e usados para adquirir itens diretamente via banco de dados.
O Cash Shop (loja virtual) é a principal fonte de receita de servidores privados de MU Online. A implementação mais comum usa um sistema web com créditos (WCoins, Cash Points ou similar) que são comprados por doação e usados para adquirir itens diretamente via banco de dados.
Arquitetura do sistema
JOGADOR → doação → gateway pagamento → webhook → script PHP
│
creditar WCoins
│
MEMB_CREDITS (banco)
│
jogador compra na loja do site
│
entregar item via SQL
│
inventário do personagem
Tabela de créditos (WCoins)
-- Tabela de créditos WCoin (Season 8+ tem MEMB_CREDITS nativa)
-- Para versões mais antigas, criar:
CREATE TABLE MEMB_CREDITS (
memb_id VARCHAR(10) NOT NULL PRIMARY KEY,
WCoin_C INT NOT NULL DEFAULT 0, -- WCoins de compra (C=Cash)
WCoin_P INT NOT NULL DEFAULT 0, -- WCoins de evento/presente (P=Point)
WCoin_G INT NOT NULL DEFAULT 0, -- WCoins de giftbox
CONSTRAINT FK_Credits_Memb FOREIGN KEY (memb_id) REFERENCES MEMB_INFO(memb_id)
);
GO
-- Inicializar para todas as contas existentes
INSERT INTO MEMB_CREDITS (memb_id)
SELECT memb_id FROM MEMB_INFO
WHERE memb_id NOT IN (SELECT memb_id FROM MEMB_CREDITS);
GO
-- Ver créditos de uma conta
SELECT m.memb_id, c.WCoin_C, c.WCoin_P
FROM MEMB_INFO m
JOIN MEMB_CREDITS c ON c.memb_id = m.memb_id
WHERE m.memb_id = 'minhaConta';
Tabela de itens da loja
-- Tabela de produtos da loja (MySQL — banco do site)
CREATE TABLE shop_items (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
item_group TINYINT NOT NULL, -- grupo do item no MU
item_index SMALLINT NOT NULL, -- ID do item no MU
item_level TINYINT NOT NULL DEFAULT 0,
item_option TINYINT NOT NULL DEFAULT 0, -- luck, exc, add
item_exc TINYINT NOT NULL DEFAULT 0, -- opções excellent
price INT NOT NULL, -- preço em WCoins
quantity INT NOT NULL DEFAULT 1, -- quantos itens por compra
image VARCHAR(255),
active TINYINT(1) NOT NULL DEFAULT 1,
category VARCHAR(50) NOT NULL DEFAULT 'geral',
sort_order INT NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Inserir alguns produtos de exemplo
INSERT INTO shop_items (name, description, item_group, item_index, item_level, item_option, price, category) VALUES
('Jewel of Bless (x10)', '10 Joias de Bless', 14, 13, 0, 0, 1000, 'joias'),
('Jewel of Soul (x10)', '10 Joias de Soul', 14, 14, 0, 0, 1000, 'joias'),
('Jewel of Life (x10)', '10 Joias de Life', 14, 31, 0, 0, 2000, 'joias'),
('Jewel of Guardian (x3)','3 Joias de Guardian', 14, 22, 0, 0, 5000, 'joias'),
('Wings of Elf +9', 'Asas de Elfa nível +9', 12, 0, 9, 4, 10000, 'asas'),
('Wings of Heaven +9', 'Asas do Céu nível +9', 12, 1, 9, 4, 10000, 'asas'),
('VIP Gold 30 dias', 'VIP Ouro por 30 dias', -1, -1, 0, 0, 15000, 'vip'),
('Reset Ticket', '1 reset grátis', -1, -2, 0, 0, 5000, 'especial');
Tabela de transações
-- Tabela de compras na loja (MySQL)
CREATE TABLE shop_transactions (
id INT AUTO_INCREMENT PRIMARY KEY,
account_id VARCHAR(10) NOT NULL,
char_name VARCHAR(16) NOT NULL,
item_id INT NOT NULL,
quantity INT NOT NULL DEFAULT 1,
cost INT NOT NULL,
status ENUM('pending','delivered','failed') DEFAULT 'pending',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
delivered_at DATETIME NULL,
FOREIGN KEY (item_id) REFERENCES shop_items(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Tabela de recargas de WCoins (MySQL)
CREATE TABLE wcoin_recharges (
id INT AUTO_INCREMENT PRIMARY KEY,
account_id VARCHAR(10) NOT NULL,
amount DECIMAL(10,2) NOT NULL, -- valor em R$
wcoin_value INT NOT NULL, -- WCoins creditados
payment_id VARCHAR(100), -- ID da transação no gateway
status ENUM('pending','completed','failed','refunded') DEFAULT 'pending',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
confirmed_at DATETIME NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
PHP — loja de itens no site
<?php
// shop.php — loja virtual integrada
require 'config/db-muserver.php';
require 'config/db-site.php'; // conexão MySQL do site
session_start();
$conta = $_SESSION['conta_mu'] ?? null;
if (!$conta) { header('Location: /login'); exit; }
$muConn = getMuDB(); // SQL Server
$siteDb = getSiteDB(); // MySQL PDO
// Buscar saldo de WCoins
$wcoinStmt = sqlsrv_query($muConn,
"SELECT WCoin_C FROM MEMB_CREDITS WHERE memb_id = ?",
[[$conta, SQLSRV_PARAM_IN]]
);
$wcoin = sqlsrv_fetch_array($wcoinStmt, SQLSRV_FETCH_ASSOC);
$saldo = $wcoin ? $wcoin['WCoin_C'] : 0;
// Buscar personagens da conta (para saber qual receberá o item)
$chars = [];
$charStmt = sqlsrv_query($muConn,
"SELECT Name FROM Character WHERE AccountID = ? AND CtlCode = 0 ORDER BY cLevel DESC",
[[$conta, SQLSRV_PARAM_IN]]
);
while ($row = sqlsrv_fetch_array($charStmt, SQLSRV_FETCH_ASSOC)) {
$chars[] = $row['Name'];
}
// Categorias de produtos
$categoria = $_GET['cat'] ?? 'joias';
$produtos = $siteDb->prepare("SELECT * FROM shop_items WHERE active = 1 AND category = ? ORDER BY sort_order, price");
$produtos->execute([$categoria]);
$produtosLista = $produtos->fetchAll();
// Processar compra
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['buy_item'])) {
$itemId = (int)$_POST['item_id'];
$charName = $_POST['char_name'];
// Verificar que o personagem pertence à conta
if (!in_array($charName, $chars)) {
$erro = "Personagem inválido";
} else {
// Buscar produto
$produto = $siteDb->prepare("SELECT * FROM shop_items WHERE id = ? AND active = 1");
$produto->execute([$itemId]);
$item = $produto->fetch();
if (!$item) {
$erro = "Produto não encontrado";
} elseif ($saldo < $item['price']) {
$erro = "Saldo insuficiente. Você tem $saldo WCoins, o item custa {$item['price']}.";
} else {
// Verificar personagem offline
$offlineStmt = sqlsrv_query($muConn,
"SELECT ConnectStat FROM Character WHERE Name = ? AND AccountID = ?",
[[$charName, SQLSRV_PARAM_IN], [$conta, SQLSRV_PARAM_IN]]
);
$charStatus = sqlsrv_fetch_array($offlineStmt, SQLSRV_FETCH_ASSOC);
if ($charStatus['ConnectStat'] == 1) {
$erro = "O personagem precisa estar OFFLINE para receber itens.";
} else {
// Debitar WCoins
sqlsrv_query($muConn,
"UPDATE MEMB_CREDITS SET WCoin_C = WCoin_C - ? WHERE memb_id = ?",
[[$item['price'], SQLSRV_PARAM_IN], [$conta, SQLSRV_PARAM_IN]]
);
// Registrar transação
$siteDb->prepare("
INSERT INTO shop_transactions (account_id, char_name, item_id, quantity, cost, status)
VALUES (?, ?, ?, 1, ?, 'pending')
")->execute([$conta, $charName, $itemId, $item['price']]);
$txId = $siteDb->lastInsertId();
// Entregar item
if (entregarItem($muConn, $charName, $item)) {
$siteDb->prepare("UPDATE shop_transactions SET status='delivered', delivered_at=NOW() WHERE id=?")->execute([$txId]);
$sucesso = "Item '{$item['name']}' entregue a $charName!";
$saldo -= $item['price'];
} else {
// Estornar WCoins se entrega falhou
sqlsrv_query($muConn,
"UPDATE MEMB_CREDITS SET WCoin_C = WCoin_C + ? WHERE memb_id = ?",
[[$item['price'], SQLSRV_PARAM_IN], [$conta, SQLSRV_PARAM_IN]]
);
$siteDb->prepare("UPDATE shop_transactions SET status='failed' WHERE id=?")->execute([$txId]);
$erro = "Erro ao entregar o item. WCoins estornados.";
}
}
}
}
}
// Função de entrega de item no inventário
function entregarItem($conn, string $charName, array $item): bool {
if ($item['item_group'] < 0) {
// Itens especiais (VIP, Reset) — tratamento diferente
return entregarEspecial($conn, $charName, $item);
}
// Encontrar slot livre no inventário
// Esta implementação depende da estrutura da tabela de inventário
// Na maioria das distribuições, a tabela Character tem a coluna Inventory (binary)
// Para inserção simples, use a tabela Character_Items (se existir) ou o storage
// Inserção no Personal Store/Vault como alternativa mais simples:
$stmt = sqlsrv_query($conn, "
INSERT INTO Character_Items (AccountID, Inventory, wPos, ItemGUID, ItemGroup, ItemIndex, ItemLevel, ...)
-- implementação depende da distribuição
");
return $stmt !== false;
}
Inventory da tabela Character — que varia por versão do MuServer. A alternativa mais prática é usar um sistema de "inbox" (caixa de entrada do jogo) ou pedir para o jogador estar no warehouse/baú para receber.Recarregar WCoins via Pix (integração com Mercado Pago)
<?php
// webhook-mp.php — receber notificações do Mercado Pago
// Verificar assinatura do webhook (segurança)
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
// Validar assinatura com sua chave secreta do MP
// (implementação específica do SDK do Mercado Pago)
$data = json_decode($payload, true);
if ($data['type'] === 'payment' && $data['action'] === 'payment.updated') {
$paymentId = $data['data']['id'];
// Buscar detalhes do pagamento via API do MP
// $payment = $mp->get("/v1/payments/$paymentId");
// Se aprovado, creditar WCoins
if ($payment['status'] === 'approved') {
$externalRef = $payment['external_reference']; // = "conta|plano" que você definiu
[$conta, $planoId] = explode('|', $externalRef);
// Tabela de planos de recarga
$planos = [
1 => ['wcoin' => 5000, 'preco' => 15.00],
2 => ['wcoin' => 12000, 'preco' => 30.00],
3 => ['wcoin' => 25000, 'preco' => 60.00],
4 => ['wcoin' => 60000, 'preco' => 120.00],
];
if (isset($planos[$planoId])) {
$wcoin = $planos[$planoId]['wcoin'];
// Creditar no SQL Server
$conn = getMuDB();
sqlsrv_query($conn,
"UPDATE MEMB_CREDITS SET WCoin_C = WCoin_C + ? WHERE memb_id = ?",
[[$wcoin, SQLSRV_PARAM_IN], [$conta, SQLSRV_PARAM_IN]]
);
// Registrar no banco do site
getSiteDB()->prepare("
UPDATE wcoin_recharges
SET status='completed', confirmed_at=NOW()
WHERE payment_id = ?
")->execute([$paymentId]);
}
}
}
http_response_code(200);
echo "OK";
Checklist da Cash Shop
BANCO DE DADOS
[ ] Tabela MEMB_CREDITS criada e populada
[ ] Tabela shop_items com produtos cadastrados
[ ] Tabela shop_transactions para histórico
[ ] Tabela wcoin_recharges para recargas
SITE
[ ] Saldo de WCoins exibido no painel do jogador
[ ] Catálogo de produtos funcionando
[ ] Processo de compra testado (com personagem offline)
[ ] Estorno automático se entrega falhar
PAGAMENTOS
[ ] Gateway de pagamento integrado (Mercado Pago/PagSeguro/Pix)
[ ] Webhook de confirmação funcionando
[ ] WCoins creditados automaticamente após pagamento
SEGURANÇA
[ ] Verificar que personagem pertence à conta logada
[ ] Verificar saldo antes de debitar
[ ] Usar transação SQL para debit+entrega
[ ] Webhook validado com assinatura
[ ] Rate limiting nas compras
Perguntas frequentes
O Cash Shop do servidor de MU funciona igual ao oficial?
Parcialmente. O WCoin shop do GameServer (Season 8+) funciona com a tabela MEMB_CREDITS. A maioria dos servidores privados implementa uma loja no site PHP que entrega itens via banco de dados, sem usar o sistema oficial do cliente.
Como os jogadores compram na Cash Shop?
O fluxo típico: jogador faz doação via Pix/cartão → site registra o pagamento → credita WCoins/pontos na conta → jogador usa os pontos na loja do site ou no Cash Shop ingame.
É possível integrar com gateway de pagamento (Mercado Pago, PagSeguro)?
Sim — ambos oferecem APIs e webhooks. Quando o pagamento é confirmado, o webhook chama seu script PHP que credita os pontos na conta do jogador.
Cash Shop com entrega automática de itens funciona sem jogar?
Sim — a entrega pode ser feita diretamente no inventário via banco SQL (se o personagem estiver offline) ou enviada para a caixa de correio do jogo.