Pagamentos no iOS fora do IAP no Brasil como implementar billing e entitlement no backend

Pagamentos no iOS fora do IAP no Brasil como implementar billing e entitlement no backend

Quando a Apple ajusta regras do iOS, quem programa sente rápido — não é “só notícia”. Segundo o Terra.com.br, a Apple atualizou o iOS no Brasil para permitir distribuição de apps fora da App Store e também pagamentos fora do mecanismo de compras no app. Na prática, isso muda arquitetura, compliance e até o jeito que você mede conversão e antifraude no seu produto.

O que a Apple mudou no Brasil (e por que isso importa para dev)

Segundo o Terra.com.br, a atualização permite que desenvolvedores:

  • Distribuam aplicativos para iOS via lojas alternativas no Brasil.
  • Processsem pagamentos fora do sistema de compras no aplicativo (IAP) da Apple.

Esse tipo de ajuste não aparece do nada. O Terra.com.br menciona que a medida reflete um acordo para resolver um caso de três anos com o Cade (Conselho Administrativo de Defesa Econômica).

Do ponto de vista técnico, existem duas frentes que você precisa entender bem: distribuição e billing/monetização.

1) Distribuição fora da App Store: impacto no seu pipeline

Quando você sai do “fluxo App Store padrão”, muda o seu pipeline de entrega. Mesmo que o iOS mantenha exigências de segurança (assinatura, verificação e notificações do sistema), você passa a lidar com:

  • Integração com o modelo de publicação/atualização da loja alternativa.
  • Possíveis diferenças em formatos de build, rollout e canais de versão.
  • Estratégias diferentes para reduzir atrito do usuário (landing, instalação, autenticação).

Na minha experiência, o maior erro aqui é achar que “é só publicar o .ipa”. Não é. A parte que quebra é o ecossistema: links de deep link, tokens de sessão, “first run” e o fluxo de validação de assinatura/conta do usuário logo após instalar.

2) Pagamentos fora do IAP: arquitetura de monetização e antifraude

O Terra.com.br também diz que a Apple e o Cade trataram proteções contra golpes e fraudes, incluindo medidas de segurança para usuários mais jovens.

Então, para dev, a pergunta vira: como você implementa billing e reconciliação sem depender do IAP?

Normalmente você precisa:

  • Um provedor de pagamento com webhooks e idempotência.
  • Validação do lado servidor (não confie no client).
  • Regras de entitlement (o que o usuário “tem direito” depois da compra).
  • Detecção de fraude (chargeback, inconsistências de device/login, padrões anômalos).

E um detalhe que pega: quando você sai do IAP, você perde uma camada “pronta” de verificações/estado que muita gente tratava como mágica. Você passa a precisar de uma máquina de estados bem desenhada no backend.

Comparando com alternativas reais: o que muda em relação ao “modelo antigo”

Antes, a maioria dos apps seguia o caminho clássico:

  • Licenças e assinaturas via IAP.
  • Estado de compra mais “integrado” ao ecossistema da Apple.
  • Menos responsabilidade direta por reconciliação e eventos de pagamento.

Agora, para quem vende fora do IAP (no Brasil), a lógica fica mais parecida com apps em outras plataformas onde a monetização é “sua”:

  • Você controla o provider e as regras de entitlement.
  • Você instrumenta métricas e experimentos por conta própria.
  • Você lida com variações de latência, falhas e reprocessamento de eventos.

Isso é bom e ruim. Bom porque destrava escolhas e pode reduzir custo/limitações do ecossistema. Ruim porque aumenta a responsabilidade de engenharia e operação.

Na Prática: como eu implementaria billing fora do IAP (passo a passo)

Vou mostrar um fluxo “realista” que uso como base em projetos. Adapte ao seu provider, mas o esqueleto é o que evita dor no pós-venda.

Passo 1: modele as entidades no backend

  • User: conta do usuário.
  • Payment: tentativa/compra (status, amount, currency).
  • Entitlement: “direito” (assinatura ativa, plano, expiração).

O porquê: se você modela entitlement como “derivado” (ex.: calculado sempre ao invés de persistido), você vai acabar com inconsistência quando receber webhooks fora de ordem.

Passo 2: crie endpoint de criação de checkout

O app chama seu backend. Seu backend cria uma sessão no provedor e retorna URLs/intent para o client abrir o checkout.

import express from "express";

const app = express();
app.use(express.json());

app.post("/api/billing/create-checkout", async (req, res) => {
  const { userId, planId } = req.body;

  // 1) valide user e plan
  // 2) aplique regras de elegibilidade (ex: idade, promoções)
  // 3) crie checkout no provedor (ex: Stripe, Adyen, etc.)
  // 4) grave o Payment com status="PENDING"

  const payment = await Payments.create({
    userId,
    planId,
    status: "PENDING",
    idempotencyKey: crypto.randomUUID(),
  });

  const checkoutSession = await provider.createCheckout({
    amountForPlan: planId,
    metadata: {
      paymentId: payment.id,
      userId,
    },
    success_url: "https://seusite.com/billing/success",
    cancel_url: "https://seusite.com/billing/cancel",
  });

  res.json({ checkoutUrl: checkoutSession.url });
});

Passo 3: receba webhooks e trate idempotência

Webhooks podem chegar duplicados, atrasados ou fora de ordem. Sem idempotência, você duplica entitlement ou “expira” ativo por engano.

app.post("/api/billing/webhook", async (req, res) => {
  const signature = req.headers["provider-signature"];
  // 1) valide assinatura do webhook (segurança)
  if (!provider.verifySignature(req.rawBody, signature)) {
    return res.status(401).send("Invalid signature");
  }

  const event = provider.parseEvent(req.body);

  const eventId = event.id; // id único do provider
  const alreadyProcessed = await WebhookEvents.exists({ eventId });
  if (alreadyProcessed) return res.status(200).send("OK");

  await WebhookEvents.insert({ eventId, receivedAt: new Date().toISOString() });

  const paymentId = event.metadata.paymentId;
  const payment = await Payments.findByPk(paymentId);

  // 2) estado transicional robusto
  // ex: se já está PAID, não regredir
  if (payment.status === "PAID" && event.type !== "chargeback") {
    return res.status(200).send("OK");
  }

  if (event.type === "checkout.session.completed") {
    await Payments.update({ status: "PAID" }, { where: { id: paymentId } });
    // 3) atualize entitlement (expiração, plano)
    await Entitlements.upsert({
      userId: payment.userId,
      planId: payment.planId,
      status: "ACTIVE",
      expiresAt: computeExpiry(payment.planId),
    });
  }

  if (event.type === "chargeback.created") {
    await Payments.update({ status: "CHARGEBACK" }, { where: { id: paymentId } });
    await Entitlements.update(
      { status: "SUSPENDED" },
      { where: { userId: payment.userId } }
    );
  }

  res.status(200).send("OK");
});

O porquê: a única fonte da verdade para status de pagamento deve ser o provedor. O app pode exibir UI otimista, mas o backend decide entitlement final.

Passo 4: no app, trate refresh de entitlement como “consulta”, não como “confiança”

No client (iOS), você:

  • Após o retorno do checkout, chama /api/me/entitlement.
  • Atualiza estado da UI com base na resposta do servidor.

Isso reduz bugs clássicos como “o usuário achou que comprou, mas o webhook ainda não chegou”.

Erros Comuns (o que evitar quando você sai do IAP)

  • Confiar no client para estado de compra: sempre valide no backend via webhooks.
  • Sem idempotência em webhooks: você paga duas vezes… ou ativa entitlement duplicado.
  • Entitlement “derivado” sem persistência: em eventos fora de ordem, você vai criar estados impossíveis.
  • Não versionar planos e promoções: quando mudar preço/condição, o backend precisa entender quais regras se aplicavam na compra original.
  • Ignorar fraude em usuários jovens: o Terra.com.br cita medidas de segurança para mais jovens. Se você não respeita isso no seu funil (verificação de conta, limites, monitoramento), você vai acumular risco e incidentes.
  • Experiência ruim no primeiro run: fora da App Store, o usuário chega por links diferentes. Se sua autenticação e onboarding não forem resilientes, churn sobe.

Como isso afeta seu dia a dia de engenharia (métricas e operação)

Você vai sentir essa mudança em três lugares:

1) Métricas de conversão e atrito

Sem IAP, o funil muda. Você precisa medir:

  • taxa de criação de checkout → sucesso
  • tempo até webhook de confirmação
  • percentual de usuários que retornam e fazem refresh de entitlement

2) Observabilidade

Quando o dinheiro passa por webhooks e filas, logs e tracing viram requisito. Eu sempre implemento:

  • correlationId por paymentId
  • logs estruturados no webhook
  • alertas quando entitlement não muda em X minutos após checkout

3) Suporte e chargeback

Você precisa de um playbook claro:

  • o que fazer quando webhook não chega
  • como reprocessar eventos
  • como lidar com chargeback/suspensão sem quebrar acesso de quem pagou corretamente

FAQ (o que devs realmente perguntam)

Isso vale só para o Brasil?

Segundo o Terra.com.br, a atualização foi anunciada para o iOS no Brasil. Estratégias fora do Brasil podem continuar exigindo o fluxo anterior, então trate por configuração/região no seu produto.

Eu preciso mudar meu backend inteiro para habilitar pagamentos fora do IAP?

Não necessariamente “inteiro”, mas você precisa adicionar: criação de checkout, webhooks com idempotência e um sistema de entitlement no backend. Se hoje você trata “compra” como evento do IAP, vai precisar desacoplar.

Como evitar fraude se o usuário compra fora do IAP?

O caminho é: validar webhooks, manter regras no backend, correlacionar device/login, monitorar padrões anômalos e considerar exigências adicionais para usuários mais jovens (o Terra.com.br menciona proteções específicas).

Como lidar com webhooks chegando duplicados?

Armazene eventId do provider e trate “already processed”. Além disso, mantenha transições seguras (não regredir status paid → pending) para não criar inconsistências.

Distribuir fora da App Store muda autenticação e licenciamento?

Muda o onboarding. Você pode receber usuários por rotas diferentes (lojas alternativas). Minha sugestão: entitlement sempre validado via backend após login, e UI que funcione mesmo se “voltar da loja” acontecer antes do processamento final.

Gostou? Me segue no GitHub e deixa um comentário se tiver dúvida ou quiser aprofundar algum ponto.

Y

Yuri Sousa

Front-End Developer / Designer

Desenvolvedor apaixonado por criar experiências digitais acessíveis e visualmente perfeitas. Escrevo sobre desenvolvimento web, design e tecnologia.