Primeiros passos
Do registro do plugin à primeira licença ativada, sem precisar de uma venda real.
1. Tenha uma conta de vendedor aprovada
O licenciamento é um serviço para quem vende. Se a sua conta ainda não é de vendedor, o pedido é feito em /vender. A aprovação é manual.
2. Registre o plugin
Em /seller/licensing/plugins/new, informe nome, apelido e plataformas. O registro gera um Product ID no formato lsp_a1b2c3d4e5f6a7b8c9d0e1f2.
O Product ID é imutável
.jar que você distribui. Nenhuma operação do painel ou da API o altera — trocar o identificador de um plugin já distribuído derrubaria todos os servidores que rodam aquela versão. Escolha o registro certo da primeira vez.O Product ID é público. Pode ficar no código à vista: quem o lê não consegue emitir nem autorizar nada com ele.
3. Configure a política
Em /seller/licensing/plugins/[id]/settings. As decisões que importam:
- Vínculo. O padrão é
INSTALLATION, e é o recomendado. Vincular por IP quebra em IP dinâmico, CGNAT e hospedagem compartilhada — cenários legítimos e comuns. - Instalações simultâneas. Quantos servidores a mesma chave cobre ao mesmo tempo.
- Prazo. Vitalícia, por período ou avaliação.
- Tolerância offline. Quantas horas o plugin segue funcionando sem alcançar a API. Setenta e duas horas é um bom padrão: cobre um fim de semana inteiro de indisponibilidade.
4. Associe o produto do marketplace
Com um produto associado, toda compra confirmada emite a licença automaticamente — o comprador vê a chave em /dashboard/licenses logo depois do pedido. Sem associação, você emite à mão.
Cada produto só pode estar em um registro. A plataforma recusa a segunda associação: dois registros apontando para o mesmo produto emitiriam duas licenças na mesma compra.
5. Integre o SDK
dependencies {
// implementation, NÃO compileOnly: a biblioteca vai DENTRO do seu .jar.
implementation("gg.lodestone:lodestone-licensing:1.0.0")
}
tasks.shadowJar {
// Obrigatório. Sem relocação, dois plugins com o SDK colidem no
// classloader do servidor e o segundo roda contra a versão do primeiro.
relocate("com.lodestone.licensing", "com.seupacote.libs.licensing")
minimize()
}public final class SeuPlugin extends JavaPlugin {
private static final String PRODUCT_ID = "lsp_a1b2c3d4e5f6a7b8c9d0e1f2";
private LodestoneLicensing licensing;
private PlatformSupport.MainThreadDispatcher mainThread;
private final AtomicBoolean premium = new AtomicBoolean(false);
@Override
public void onEnable() {
saveDefaultConfig();
String key = getConfig().getString("license.key", "");
if (key.isBlank()) {
getLogger().severe("Preencha license.key no config.yml.");
getServer().getPluginManager().disablePlugin(this);
return;
}
mainThread = PlatformSupport.dispatcher(this);
licensing = LodestoneLicensing.builder()
.productId(PRODUCT_ID)
.licenseKey(key)
.dataFolder(getDataFolder().toPath())
.platform(PlatformSupport.detect().id())
.serverVersion(PlatformSupport.serverVersion())
.listener(new LicenseListener() {
@Override public void onAuthorized(LicenseResult r) {
mainThread.run(() -> premium.set(true));
}
@Override public void onDenied(LicenseResult r) {
mainThread.run(() -> {
premium.set(false);
getLogger().warning(r.message());
});
}
@Override public void onGrace(LicenseResult r) {
// Plataforma fora do ar: SEGUE ligado.
mainThread.run(() -> premium.set(true));
}
@Override public void onOffline(LicenseResult r) {
mainThread.run(() -> premium.set(false));
}
})
.build();
// O boot NÃO espera a rede.
licensing.activateAsync();
}
@Override
public void onDisable() {
if (licensing != null) licensing.close();
}
}Quatro regras que o exemplo demonstra
- O boot não espera a rede. Um servidor que trava 15 segundos no
onEnable()porque a rede do cliente está ruim é um chamado de suporte garantido. - Os recursos começam desligados. Ligar primeiro e desligar depois entrega o plugin a quem bloquear a nossa API no firewall.
- Os callbacks voltam para a thread principal. Eles chegam na thread do SDK; tocar a API do Bukkit de lá é erro na maioria das versões.
onGracemantém tudo ligado. É a situação “a plataforma caiu” — e o cliente não tem culpa disso.
6. Teste sem uma venda real
Emita uma licença de teste na tela de licenças do plugin, marcando licença de teste. Ela não conta para o limite do plano.
license:
key: "LSL-XXXXX-XXXXX-XXXXX-XXXXX"
endpoint: "http://localhost:3000/api/v1/licensing"
test-mode: true # sem isto, a licença de teste é RECUSADALicença de teste não autoriza produção
test_license_in_production, o documento assinado sai com test: true, e o SDK recusa quando testMode está desligado. Isso impede que uma chave do Playground escape para o cliente final.Para exercitar os cenários de falha — expirada, revogada, limite atingido, resposta adulterada, API fora do ar — use o Playground. Ele roda contra o motor real, não contra uma simulação.
7. Publique
Gere o .jar com o SDK sombreado e relocado, e publique o produto no marketplace. A partir da primeira compra confirmada, a licença é emitida sozinha.
Nunca coloque uma chave de API no .jar
.jar é um zip.