Flutter descomplicando as compras no aplicativo

Tempo de leitura: 5 minutes

Então, por que nas compras de aplicativos?

Bem, basicamente, essas compras no aplicativo são necessárias quando vendemos conteúdo digital.

Um relatório da Statista estimou que, em 2021, os aplicativos móveis podem gerar mais de 693 bilhões de dólares em receitas usando estratégias de monetização de aplicativos, como compras no aplicativo, publicidade no aplicativo e downloads pagos. Portanto, posso dizer com certeza que você deve pensar em adicionar a implementação de compra no aplicativo ao seu aplicativo.

Então é hora de aprender como implementá-lo em seu aplicativo.

Quando é implementar a compra in-app usando o pacote in_app_purchase oficial do qual tenho observado muitas pessoas reclamando, o que os faz preferir abordagens como Revenue ou Strip, mas posso dizer que é mais simples do que parece.

deixe-me fazer você entender isso de uma vez por todas.

Esta seção tem exemplos de código para as seguintes tarefas:

  • Conectando-se à loja subjacente
  • Carregando produtos para venda
  • Ouvindo atualizações de compra
  • Fazendo uma compra
  • Restaurando compras anteriores
  • Atualizar ou fazer downgrade de uma assinatura existente no aplicativo
  • Apresentando uma planilha de resgate de código (iOS 14)

 

Primeiros passos

O plugin in_app_purchase depende da App Store e do Google Play para fazer compras no aplicativo. mas você ainda precisa entender e configurar seu aplicativo em cada loja. Você pode verificar a documentação de cada loja

Você também pode verificar o exemplo README de compra no aplicativo para ajudá-lo a configurar seu projeto antes de começar.

Me conte nos comentários como foi 😉 assim saberei se preciso escrever um artigo especialmente para isso.

Depois de concluirmos as etapas nativas e termos um aplicativo assinado na Google Play Store e na App Store e adicionarmos produtos a eles, vamos passar para o código.

Primeiro precisamos adicionar o pacote no arquivo pubspec.yaml

in_app_purchase: ^3.1.11

Vamos ver os diferentes tipos de produtos que você pode adquirir com este pacote.

Consumível: serviços únicos, como comida de peixe em um aplicativo de pesca

Não Consumível: só precisa ser adquirido uma vez pelos usuários. Serviços que não expiram ou diminuem com o uso, como novas pistas de corrida para um aplicativo de jogo.

Assinatura: Assinaturas auto-renováveis permitem que o usuário compre atualização e conteúdo dinâmico por um determinado período de tempo

 

Conectando-se às lojas e carregando produtos

Certifique-se de seguir as etapas anteriores e criar produtos nas lojas

Precisamos verificar a disponibilidade das lojas antes de carregar os detalhes dos produtos

final bool available = await InAppPurchase.instance.isAvailable();
if (!available) {
  // não consigo se conectar com lojas. Atualize a IU adequadamente.
}else{
  // carregar detalhes dos produtos.
}

Agora que verificamos a disponibilidade da loja é hora de carregar os detalhes dos produtos, para isso temos o método queryProductDetails(your_product_id), este método solicita que a loja retorne os detalhes do produto para esses IDs de produtos específicos (certifique-se de que você já tenha esses IDs de produtos criada)

final productsIds = <String>{'product1', 'product2'};
final ProductDetailsResponse response = await InAppPurchase.instance.queryProductDetails(productsIds);
if (response.notFoundIDs.isNotEmpty) {
  // Lide com o erro.
}
List<ProductDetails> products = response.productDetails;

Fizemos uma consulta básica onde obtemos todos os produtos cujos ids correspondem ao que fornecemos.

 

Ouvindo atualizações de compra

No método initState do seu aplicativo, inscreva-se em todas as compras recebidas. Você deve sempre começar a ouvir as atualizações de compra o mais cedo possível para poder acompanhar todas as atualizações de compra, incluindo as da sessão anterior do aplicativo. Para ouvir a atualização:

class _MyAppState extends State<MyApp> {
  late StreamSubscription<List<PurchaseDetails>> _subscription;
  @override
  void initState() {
    final Stream purchaseUpdated = InAppPurchase.instance.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
      _listenToPurchaseUpdated(purchaseDetailsList);
    }, onDone: () {
      _subscription.cancel();
    }, onError: (error) {
      // lidar com o erro aqui.
    });
super.initState();
  }
  @override
  void dispose() {
    
    // Sempre cancelar em Dispose
    _subscription.cancel();
    super.dispose();
  }

É isso aí 🙂 agora você só precisa lidar com atualizações de compra como esta:

void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
  purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
    if (purchaseDetails.status == PurchaseStatus.pending) {
      _showPendingUI();
    } else {
      if (purchaseDetails.status == PurchaseStatus.error) {
        _handleError(purchaseDetails.error!);
      } else if (purchaseDetails.status == PurchaseStatus.purchased || purchaseDetails.status == PurchaseStatus.restored) {
        bool valid = await _verifyPurchase(purchaseDetails);
        if (valid) {
          _deliverProduct(purchaseDetails);
        } else {
          _handleInvalidPurchase(purchaseDetails);
        }
      }
      if (purchaseDetails.pendingCompletePurchase) {
        await InAppPurchase.instance.completePurchase(purchaseDetails);
      }
    }
  });
}

Simples, certo? acalme-se, vamos decodificar

Este método _listenToPurchaseUpdated manipula os estados de atualização de compras.

Vamos nos concentrar em PurchaseDetails.status == PurchaseStatus.purchased || buyDetails.status == PurchaseStatus.restored . Isso acontece quando o produto/assinatura foi adquirido com sucesso ou restaurado e quando isso acontece você sempre sempre sempre, repito SEMPRE… “deve verificar a compra no backend antes de entregá-la ao usuário”, adicionamos esta lógica no método _verifyPurchase.

Se a validação de back-end foi bem-sucedida, você deverá chamar InAppPurchase.instance.completePurchase(purchaseDetails)

Chamar InAppPurchase.completePurchase informará à loja subjacente que o aplicativo verificou e processou a compra e a loja pode finalizar a transação e cobrar a conta de pagamento do usuário final

 

O que enviar ao backend para ser validado? 🤔

Você pode pegar esses parâmetros para enviar para o back-end desta forma 🙂 simples assim

Future<bool> verifyPurchase(PurchaseDetails purchaseDetails) async {

    if (Platform.isAndroid) {
      final localDataVerification = json.decode(purchaseDetails.verificationData.localVerificationData) as Map<String, dynamic>;
      final orderId = localDataVerification['orderId'] as String;
      final productId = localDataVerification['productId'] as String;
      final packageName = localDataVerification['packageName'] as String;
      final token = localDataVerification['purchaseToken'] as String;
    } else if (Platform.isIOS) {
      final appStorePurchaseDetails = purchaseDetails as AppStorePurchaseDetails;
      final paymentToken = appStorePurchaseDetails.verificationData.localVerificationData;
      final transitionId = appStorePurchaseDetails.skPaymentTransaction.originalTransaction?.transactionIdentifier;
      final storeId = purchaseDetails.productID;
    }

    return Future<bool>.value(true);
  }

Você precisará importar in_app_purchase_storekit/in_app_purchase_storekit.dart para poder usar AppStorePurchaseDetails, pois ele acessa propriedades de compra específicas da plataforma IOS

Pode encontrar mais informação aqui

 

Fazendo uma compra

De acordo com o documento

O InAppPurchase.purchaseStream enviará atualizações de compra após iniciar o fluxo de compra usando InAppPurchase.buyConsumable ou InAppPurchase.buyNonConsumable. Após verificar o recibo de compra e entregar o conteúdo ao usuário, é importante ligar para InAppPurchase.completePurchase para informar à loja subjacente que a compra foi concluída.

É super simples fazer uma compra

PurchaseParam? purchaseParam;

if (Platform.isAndroid) {
    purchaseParam = GooglePlayPurchaseParam(
    productDetails: productDetails // Salvo anteriormente em queryProductDetails(),
    applicationUserName: '',
    );
} else if (Platform.isIOS) {
    purchaseParam = PurchaseParam(
    productDetails: productDetails,
    applicationUserName: '',
    );
}

// Para produtos não consumíveis, por exemplo, produtos/assinatura
await InAppPurchase.buyNonConsumable(purchaseParam: purchaseParam);

// Para consumíveis, por exemplo, vida de jogo
// await InAppPurchase.byConsumable(purchaseParam: purchaseParam);

Ambas as lojas subjacentes lidam com produtos consumíveis e não consumíveis de forma diferente. Você precisa fazer uma distinção aqui e definir o método de compra correto para cada tipo.

 

Restaurar uma compra

Isso é moleza 😋 só precisa chamar o seguinte método

await InAppPurchase.instance.restorePurchases();

 

Atualizar ou fazer downgrade de uma assinatura existente no aplicativo

Para fazer upgrade/downgrade de uma assinatura no aplicativo existente no Google Play, você precisa fornecer uma instância de ChangeSubscriptionParam com o PurchaseDetails antigo do qual o usuário precisa migrar e um ProrationMode opcional com o objeto GooglePlayPurchaseParam ao chamar InAppPurchase.buyNonConsumable.

A App Store não exige isso porque fornece um mecanismo de agrupamento de assinaturas. Cada assinatura que você oferece deve ser atribuída a um grupo de assinaturas. Agrupar assinaturas relacionadas pode ajudar a evitar que os usuários comprem acidentalmente várias assinaturas. Consulte a seção Criando um grupo de assinaturas do guia de assinatura da Apple.

final PurchaseDetails oldPurchaseDetails = ...; // Você precisará adicionar sua lógica para obter os detalhes da compra antiga

PurchaseParam purchaseParam = GooglePlayPurchaseParam(
    productDetails: productDetails,
    changeSubscriptionParam: ChangeSubscriptionParam(
        oldPurchaseDetails: oldPurchaseDetails,
        prorationMode: ProrationMode.immediateWithTimeProration),
);

InAppPurchase.instance.buyNonConsumable(purchaseParam: purchaseParam);

 

Apresentando uma planilha de resgate de código (iOS 14)

Para Android, de acordo com a documentação oficial,

Se seu aplicativo for compatível com o fluxo de trabalho de compra no aplicativo (descrito em Como fazer solicitações In-app Billing), seu aplicativo será automaticamente compatível com o resgate de códigos promocionais no aplicativo.

Portanto, nosso aplicativo, por padrão, quando adicionamos suporte de compra no aplicativo, ele está automaticamente preparado para lidar com o resgate de códigos promocionais. Vamos nos preocupar apenas com a Apple agora 🙂

O código a seguir exibe uma planilha que permite ao usuário resgatar códigos de oferta que você configurou no App Store Connect. Para obter mais informações sobre o resgate de códigos de oferta, consulte Implementação de códigos de oferta em seu aplicativo.

InAppPurchaseStoreKitPlatformAddition iosPlatformAddition = InAppPurchase.getPlatformAddition<InAppPurchaseStoreKitPlatformAddition>();
iosPlatformAddition.presentCodeRedemptionSheet();

Nota: O InAppPurchaseStoreKitPlatformAddition é definido no arquivo in_app_purchase_storekit.dart, portanto, você precisa importá-lo para o arquivo que usará InAppPurchaseStoreKitPlatformAddition:

import ‘package:in_app_purchase_storekit/in_app_purchase_storekit.dart’;

 

Um bônus incrível para você 😊

Desenvolvi um projeto utilizando compra in-app e riverpod como gerenciamento de estado, você pode tudo o que aprendemos na prática

Código fonte do projeto (Git)

 

É isso aí 💪🏿 agora você está pronto para comprar no aplicativo

Espero que você tenha gostado deste passeio por meio da compra no aplicativo e agora você vê que não é tão complicado quanto parecia.

Siga-me para mais dicas sobre Flutter 🙂