Voltar para o Blog
Quest Log

Compra no App (IAP) na Unity: Configurar do Zero

Moedas douradas e um baú saindo da tela de um celular com um botão de comprar brilhando

Unity in app purchase do zero com IAP 5: catálogo consumível e não consumível, código C# de compra e restauração, produto no Play Console e teste.

Compra no App (IAP) na Unity: Configurar do Zero

Para fazer unity in app purchase hoje você usa o pacote Unity IAP 5 (com.unity.purchasing), que no Unity 6.3 LTS está na versão 5.4.4. O fluxo é: instalar o pacote, declarar os produtos com o mesmo ID que você cria no Google Play Console, conectar o StoreController, comprar, entregar o item e confirmar a compra. O teste acontece com uma conta de testador de licença, sem cobrança.

O motivo deste post existir: a versão 5 do pacote mudou a API inteira, e boa parte dos tutoriais ainda mostra IStoreListener e UnityPurchasing.Initialize, que pertencem à linha 4. Aqui o código é da API atual, do zero até uma compra de teste aprovada.

Tipos de produto: consumível, não consumível, recorrente

Antes do código, decida o que você vende. O tipo muda como a compra é entregue e restaurada.

  • Consumível (ProductType.Consumable): pode ser comprado várias vezes. Moedas, vidas, pacote de energia. A loja não guarda histórico para você restaurar: quem guarda o saldo é o seu jogo (save local ou servidor).
  • Não consumível (ProductType.NonConsumable): compra única e permanente. Remover anúncios, desbloquear o jogo completo, uma skin. A loja lembra, e o jogador recupera ao reinstalar.
  • Recorrente (ProductType.Subscription): cobrança periódica, como passe VIP mensal. Exige tratar renovação, cancelamento e período de carência. Para um primeiro jogo, deixe de fora.

Para um jogo indie, a combinação que mais funciona é um não consumível "remover anúncios" e dois ou três pacotes consumíveis. Se você ainda está decidindo o modelo de receita, o post sobre modelos de monetização de jogos ajuda a escolher antes de programar.

Instalar e configurar o Unity IAP

  1. Abra Window > Package Management > Package Manager, procure In-App Purchasing no Unity Registry e instale. Confira se a versão é 5.x.
  2. Em File > Build Profiles, deixe Android como plataforma ativa.
  3. Abra Project Settings > Services > In-App Purchasing e confirme que o serviço está ligado. Em Google Play Configuration, cole a chave pública de licença do seu app (Play Console > Monetizar com o Google Play > Configuração da monetização). Ela é usada para conferir localmente se o recibo veio mesmo do Google.
  4. Gere um AAB e envie para a faixa de teste interno. O Play Console só libera a criação de produtos no app depois que recebe um build que contém a biblioteca de faturamento.

O pacote 5.4 já traz a Google Play Billing Library 9.0.0 (segundo o changelog do pacote). Isso importa porque a Google exige Billing Library 8 ou superior para apps novos e atualizações desde 31/08/2026, conforme a página de descontinuação da Billing Library no Android Developers. Pacote IAP antigo é motivo de rejeição de atualização.

Unity IAP: catálogo de produtos

O catálogo é a lista de produtos que o jogo conhece. Use IDs em minúsculas, sem espaço, no formato nomedojogo.item. O mesmo ID vai no Play Console, letra por letra.

Para manter IDs num lugar só, crie uma classe estática. Arquivo ProductIds.cs:

public static class ProductIds
{
    public const string Coins100 = "meujogo.moedas100";
    public const string Coins500 = "meujogo.moedas500";
    public const string RemoveAds = "meujogo.removeranuncios";
}

Constante evita o erro mais bobo e mais comum de IAP: digitar o ID diferente em dois lugares.

Unity in app purchase: código de compra em C#

O StoreController da IAP 5 trabalha com eventos. A regra mais importante da documentação: assine todos os eventos antes de chamar Connect(), porque compras pendentes de uma sessão anterior podem chegar logo na reconexão.

O fluxo de compra tem duas etapas. O jogador compra, a loja aprova e você recebe um PendingOrder em OnPurchasePending. Você entrega o item e chama ConfirmPurchase. Se o jogo fechar entre a aprovação e a confirmação, a compra pendente volta na próxima abertura, e o jogador não perde nada.

Arquivo IapManager.cs:

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Purchasing;

public class IapManager : MonoBehaviour
{
    [SerializeField] private PlayerWallet wallet;

    private StoreController store;
    private bool productsReady;

    private async void Awake()
    {
        store = UnityIAPServices.StoreController();

        store.OnPurchasePending += HandlePurchasePending;
        store.OnPurchaseConfirmed += HandlePurchaseConfirmed;
        store.OnPurchaseFailed += HandlePurchaseFailed;
        store.OnPurchaseDeferred += HandlePurchaseDeferred;
        store.OnStoreConnected += HandleStoreConnected;
        store.OnStoreDisconnected += failure => Debug.LogError("Loja desconectada: " + failure.Message);
        store.OnProductsFetched += HandleProductsFetched;
        store.OnProductsFetchFailed += failure => Debug.LogError("Falha ao buscar produtos: " + failure.FailureReason);
        store.OnPurchasesFetched += HandlePurchasesFetched;

        await store.Connect();
    }

    private void HandleStoreConnected()
    {
        List<ProductDefinition> catalog = new List<ProductDefinition>
        {
            new ProductDefinition(ProductIds.Coins100, ProductType.Consumable),
            new ProductDefinition(ProductIds.Coins500, ProductType.Consumable),
            new ProductDefinition(ProductIds.RemoveAds, ProductType.NonConsumable)
        };

        store.FetchProducts(catalog);
    }

    private void HandleProductsFetched(List<Product> products)
    {
        productsReady = true;
        // Compras só podem ser buscadas para produtos que a loja já devolveu.
        store.FetchPurchases();
    }

    public void Buy(string productId)
    {
        if (!productsReady)
        {
            Debug.LogWarning("Produtos ainda não carregaram.");
            return;
        }

        store.PurchaseProduct(productId);
    }

    public string GetPrice(string productId)
    {
        Product product = store.GetProductById(productId);
        return product != null ? product.metadata.localizedPriceString : "";
    }

    private void HandlePurchasePending(PendingOrder order)
    {
        Product product = order.CartOrdered.Items().FirstOrDefault()?.Product;
        if (product == null)
        {
            return;
        }

        Grant(product.definition.id);
        store.ConfirmPurchase(order);
    }

    private void HandlePurchaseConfirmed(Order order)
    {
        if (order is FailedOrder failed)
        {
            Debug.LogError("Confirmação falhou: " + failed.FailureReason);
        }
    }

    private void HandlePurchaseFailed(FailedOrder failed)
    {
        Debug.LogWarning("Compra falhou: " + failed.FailureReason + " " + failed.Details);
    }

    private void HandlePurchaseDeferred(DeferredOrder deferred)
    {
        Debug.Log("Compra aguardando pagamento ou aprovação.");
    }

    private void HandlePurchasesFetched(Orders orders)
    {
        foreach (ConfirmedOrder confirmed in orders.ConfirmedOrders)
        {
            Product product = confirmed.CartOrdered.Items().FirstOrDefault()?.Product;
            if (product != null && product.definition.type == ProductType.NonConsumable)
            {
                Grant(product.definition.id);
            }
        }
    }

    private void Grant(string productId)
    {
        switch (productId)
        {
            case ProductIds.Coins100: wallet.AddCoins(100); break;
            case ProductIds.Coins500: wallet.AddCoins(500); break;
            case ProductIds.RemoveAds: wallet.SetAdsRemoved(true); break;
        }
    }
}

O PlayerWallet é o seu sistema de save: um componente com AddCoins(int amount) e SetAdsRemoved(bool value) que grava em disco na hora. Gravar imediatamente importa: se você só salva ao sair da fase e o jogo fecha, a moeda comprada some.

Nos botões da loja, chame Buy(ProductIds.Coins100) e mostre o preço com GetPrice. O preço vem da loja já em reais e formatado, então nunca escreva preço fixo no texto do botão.

Restaurar compras e validar recibo

No Android, FetchPurchases(), chamado depois que os produtos chegam, já devolve as compras confirmadas da conta Google do aparelho, e o HandlePurchasesFetched reaplica os não consumíveis. Reinstalou, abriu, o "remover anúncios" volta sozinho.

No iOS, a Apple exige um botão de restaurar compras visível. Ele chama store.RestoreTransactions(callback), e cada compra restaurada passa de novo por OnPurchasePending. Como Grant de um não consumível é idempotente (ligar algo que já está ligado), entregar duas vezes não causa dano. Para consumível, a restauração não se aplica.

Sobre validação: a checagem local de recibo com a chave pública barra o golpe mais simples, mas um aparelho modificado pode enganar o próprio jogo. Se a compra dá vantagem em ranking ou multiplayer, valide no servidor consultando a API de desenvolvedor do Google Play antes de entregar. O post sobre compras no app em jogos explica a lógica de validação e o design dos produtos, independente da engine.

Neste ponto você tem loja, catálogo e compra funcionando. O que costuma travar quem faz sozinho vem depois: integrar a moeda comprada com a economia do jogo, salvar sem perder progresso, montar a UI da loja e equilibrar tudo para a compra parecer justa. É o tipo de costura entre sistemas que um projeto guiado resolve melhor do que tentativa e erro.

Curso de Unity
Quer fazer seu primeiro jogo na Unity?

No curso de Unity 6 da CursoGame.Dev você vai do zero ao primeiro jogo, com um desafio prático em cada level e suporte individual no WhatsApp quando travar.

Conhecer o curso de Unity
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

Criar o produto no Google Play Console

Com o build no teste interno:

  1. No Play Console, abra o app e vá em Monetizar com o Google Play > Produtos > Produtos no app.
  2. Clique em Criar produto. O ID do produto é o mesmo da constante (meujogo.moedas100). Ele não pode ser alterado nem reaproveitado depois.
  3. Preencha nome e descrição (aparecem na tela de compra do Google).
  4. Defina o preço em reais. O Console sugere conversão para outros países; revise os principais.
  5. Salve e clique em Ativar. Produto inativo não aparece para ninguém.

Quanto à taxa, a página de taxas de serviço da central de ajuda do Play Console, consultada em outubro de 2026, lista para os mercados que ainda seguem a estrutura anterior (o que inclui o Brasil na data da consulta) 15% sobre o primeiro US$ 1 milhão de receita por ano e 30% acima disso em transações comuns, e 15% em produtos recorrentes. A própria página informa que uma estrutura nova já vale em alguns mercados e será expandida, então confira antes de precificar.

Testar com license testers

  1. Em Configuração > Teste de licença (na página inicial do Play Console, fora do app), adicione os e-mails das contas Google que vão testar.
  2. Adicione as mesmas contas como testadoras da faixa de teste interno do app.
  3. No celular, logado com essa conta, abra o link de participação e instale o jogo pela Play Store. Instalação por cabo não conversa com o faturamento do jeito certo.
  4. Compre. A tela do Google mostra métodos de teste ("cartão de teste, sempre aprovado", "sempre recusado", entre outros). Teste os dois caminhos.

Teste também: fechar o app no meio da compra e reabrir (a pendência tem que ser entregue), comprar sem internet, e reinstalar para ver o não consumível voltar.

Erros comuns: produto não encontrado, compra pendente

  • Produto não aparece / GetProductById volta nulo: ID divergente, produto não ativado, conta do aparelho fora da lista de testadores ou app instalado fora da faixa de teste. Também acontece nas primeiras horas depois de criar o produto.
  • Compra pendente que nunca conclui: com método de pagamento que demora a compensar, a loja entrega OnPurchaseDeferred. Não entregue o item ainda: ele chega por OnPurchasePending quando o pagamento compensar.
  • Item entregue duas vezes: você entregou em OnPurchasePending e de novo em outro lugar. Centralize em Grant.
  • Item comprado e não entregue: esqueceu de chamar ConfirmPurchase, e o Google reembolsa compras não confirmadas depois de alguns dias.
  • Código de tutorial antigo não compila: IStoreListener, ConfigurationBuilder e ProcessPurchase são da IAP 4. Use a API do StoreController.
  • Erro no editor: no Play Mode a IAP usa uma loja falsa para teste rápido. Ela não valida ID nem preço reais, então a validação de verdade é sempre no aparelho.

Próximo passo

Implemente só o "remover anúncios" primeiro: um produto, não consumível, testável em uma tarde. Quando ele estiver comprando, restaurando e sobrevivendo a reinstalação, acrescente os consumíveis. Para o caminho completo do build até a loja, veja como publicar jogo Unity na Play Store.

Perguntas frequentes

Qual versão do Unity IAP usar no Unity 6.3?

A página do pacote no manual do Unity 6.3 lista o com.unity.purchasing 5.4.4. A linha 5 trocou a API antiga (IStoreListener e UnityPurchasing.Initialize) pelo StoreController, então tutoriais anteriores a 2025 não compilam sem adaptação.

Unity IAP funciona com a Google Play Billing exigida em 2026?

Sim. O changelog do pacote mostra que a versão 5.4.0 atualizou a Google Play Billing Library para a 9.0.0. A Google exige Billing Library 8 ou superior em apps novos e atualizações desde 31/08/2026, então qualquer 5.4.x atende.

Por que meu produto IAP não aparece no jogo?

As causas mais comuns são ID diferente entre o código e o Play Console, produto inativo, app instalado fora de uma faixa de teste, ou conta do celular que não é testadora. Também é preciso ter enviado ao menos um build com a biblioteca de faturamento para o Console liberar a criação de produtos.

Como testar compra no app sem pagar?

Cadastre seu e-mail como testador de licença no Play Console e instale o jogo por uma faixa de teste (interno ou fechado). Na hora da compra aparecem cartões de teste que aprovam ou recusam sem cobrança real.