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.