Integrar a biblioteca do Google Play Faturamento para PC nativo ao seu app

Gere receita com seu jogo vendendo produtos digitais usando o Play Faturamento. O SDK oferece APIs para mostrar produtos disponíveis para compra, iniciar o fluxo de compra e processar compras. As chamadas para essas APIs de faturamento são feitas usando a Conta do Google que iniciou o jogo no cliente do Google Play Games e não exigem etapas de login adicionais.

Se você fez a integração com a biblioteca Android Play Faturamento, essas APIs Play Faturamento não serão novidade. Todas as integrações do lado do servidor com o Play Faturamento podem ser reutilizadas por títulos para PC, já que são as mesmas no Android e no PC.

Pré-requisitos

Etapa 1: consultar compras anteriores e compras concluídas fora do aplicativo

Quando o aplicativo for iniciado ou voltar ao primeiro plano, consulte as compras. Isso é necessário para detectar compras que ocorreram fora do jogo ou para desbloquear o acesso a compras feitas anteriormente pelo usuário.

  1. Consulte compras usando BillingClient::QueryPurchases.

  2. Continue processando as compras.

// Query for purchases when:
// - Application starts up
// - Application window re-enters the foreground
auto promise = std::make_shared<std::promise<QueryPurchasesResult>>();
billing_client.QueryPurchases([promise](QueryPurchasesResult result) {
   promise->set_value(std::move(result));
});

auto query_purchases_result = promise->get_future().get();
if (query_purchases_result.ok()) {
  auto purchases = query_purchases_result.value().product_purchase_details;
  // Process the purchases
} else {
  // Handle the error
}

Etapa 2: mostrar produtos disponíveis para compra

Tudo pronto para você consultar seus produtos disponíveis e mostrá-los aos usuários. Consultar detalhes do produto é uma etapa importante antes de mostrar seus produtos para os usuários, já que retorna informações localizadas do produto.

Antes de oferecer um produto para venda, verifique se o usuário ainda não tem o produto. Se o usuário tiver um produto consumível que ainda está no histórico de compras, ele precisa consumir esse produto antes de fazer uma nova compra.

  1. Consulte os detalhes do produto usando BillingClient::QueryProductDetails. Transmita os IDs dos produtos que você registrou no Google Play Console.
  2. Renderize o ProductDetails, que inclui o nome localizado e o preço da oferta do produto.
  3. Mantenha uma referência ao offer_token do produto. Isso é usado para iniciar um fluxo de compra da oferta.
QueryProductDetailsParams params;
params.product_ids.push_back({"example_costmetic_1", ProductType::kTypeInApp});
params.product_ids.push_back({"example_costmetic_1", ProductType::kTypeInApp});
params.product_ids.push_back({"example_battle_pass", ProductType::kTypeInApp});

auto promise = std::make_shared<std::promise<QueryProductDetailsResult>>();
billing_client.QueryProductDetails(params, [promise](QueryProductDetailsResult result) {
   promise->set_value(std::move(result));
});

auto query_product_details_result = promise->get_future().get();
if (query_product_details_result.ok()) {
   auto product_details = query_product_details_result.value().product_details;
   // Display the available products and their offers to the user
} else {
   // Handle the error
}

Etapa 3: iniciar um fluxo de compra

Quando o usuário mostrar a intenção de comprar um produto que você mostrou, inicie o fluxo de compra.

  1. Comece chamando BillingClient::LaunchPurchaseFlow(). Transmita o offer_token obtido ao consultar os detalhes do produto.
  2. Quando a compra for concluída, a função de continuação será chamada com o resultado.
  3. Se for bem-sucedida, a continuação vai conter um ProductPurchaseDetails. Continue processando a compra.
LaunchPurchaseFlowParams params { product_offer.offer_token };

auto promise = std::make_shared<std::promise<LaunchPurchaseFlowResult>>();
billing_client.LaunchPurchaseFlow(params, [promise](LaunchPurchaseFlowResult result) {
   promise->set_value(std::move(result));
});
// The purchase flow has started and is now in progress.

auto launch_purchase_flow_result = promise->get_future().get();

// The purchase flow has now completed.
if (launch_purchase_flow_result.ok()) {
   auto purchase = launch_purchase_flow_result.value().product_purchase_details;
   // Process the purchase
} else if (launch_purchase_flow_result.code() == BillingError::kUserCanceled) {
   // Handle an error caused by the user canceling the purchase flow
} else {
   // Handle any other error codes
}

Etapa 4: processar uma compra

Processar com um servidor de back-end

Para jogos com um servidor de back-end, conclua o processamento enviando o purchase_token ao servidor de back-end. Conclua o restante do processamento usando as APIs do Google Play Faturamento do lado do servidor. Essa integração do lado do servidor é a mesma feita para um jogo Android integrado ao Google Play Faturamento.

void ProcessPurchasesWithServer(std::vector<ProductPurchaseDetails> purchases) {
   std::vector<std::string> purchase_tokens;
   for (const auto& purchase : purchases) {
      purchase_tokens.push_back(purchase.purchase_token);
   }

   // Send purchase tokens to backend server for processing
}

Processar sem um servidor de back-end

  1. Verifique se o pagamento do usuário não está pendente conferindo se ProductPurchaseDetails::purchase_state é PurchaseState::kPurchaseStatePurchased. Se o estado da compra for pendente, notifique o usuário de que ele precisa concluir etapas adicionais antes de receber o produto comprado.

  2. Conceda ao usuário acesso ao produto comprado e atualize o armazenamento de direitos do seu jogo.

  3. Para compras de produtos que não são de consumo (produtos que só podem ser comprados uma vez), verifique se a compra já foi confirmada usando ProductPurchaseDetails::is_acknowledged.

    1. Se a compra não tiver sido confirmada, notifique o Google de que o usuário está recebendo um direito ao produto chamando BillingClient::AcknowledgePurchase.
  4. Para compras de produtos consumíveis (que podem ser comprados mais de uma vez), notifique o Google de que o usuário está recebendo um direito ao produto chamando BillingClient::ConsumePurchase.

void ProcessPurchasesWithoutServer(std::vector<ProductPurchaseDetails> purchases) {
   std::vector<std::string> entitled_product_ids;
   for (const auto& purchase : purchases) {
      auto was_successful = ProcessPurchasePurchaseWithoutServer(purchase);
      if (was_successful) {
         entitled_product_ids.push_back(purchase.product_id);
      }
   }

   // Note that non-consumable products that were previously purchased may have
   // been refunded. These purchases will stop being returned by
   // `QueryPurchases()`. If your game has given a user access to one of these
   // products storage they should be revoked.
   //
   // ...
}

bool ProcessPurchasePurchaseWithoutServer(ProductPurchaseDetails purchase) {
   auto is_purchase_completed =
      purchase.purchase_state == PurchaseState::kPurchaseStatePurchased;
   if (!is_purchase_completed) {
      // Notify the user that they need to take additional steps to complete
      // this purchase.
      return false;
   }

   // Determine if the product ID is associated with a consumable product.
   auto is_consumable = IsConsumableProductId(purchase.product_id);
   if (is_consumable) {
      // Grant an entitlement to the product to the user.
      // ...
      // Then, notify Google by consuming the purchase.

      ConsumePurchaseParams params { purchase.purchase_token };
      auto promise = std::make_shared<std::promise<ConsumePurchaseResult>>();
      billing_client.ConsumePurchase(params, [promise](ConsumePurchaseResult result) {
         promise->set_value(std::move(result));
      });

      auto consume_purchase_result = promise->get_future().get();
      if (!consume_purchase_result.ok()) {
         // Examine the failure code & message for more details & notify user
         // of failure.
         // ...
         return false;
      }

      return true;
   }

   // Otherwise the product is assumed to be a non-consumable.

   // Grant an entitlement to the product to the user.
   // ...
   // Then, notify Google by acknowledging the purchase (if not already done).

   if (purchase.is_acknowledged) {
      return true;
   }

   AcknowledgePurchaseParams params { purchase.purchase_token };
   auto promise = std::make_shared<std::promise<AcknowledgePurchaseResult>>();
   billing_client.AcknowledgePurchase(params, [promise](AcknowledgePurchaseResult result) {
      promise->set_value(std::move(result));
   });

   auto acknowledge_purchase_result = promise->get_future().get();
   if (!acknowledge_purchase_result.ok()) {
      // Examine the failure code & message for more details & notify user
      // of failure.
      // ...
      return false;
   }

   return true;
}

Verificação de compra do lado do cliente

Você recebe o signature de ProductPurchaseDetails. O campo signature é assinado com sua chave privada usando o algoritmo de assinatura SHA1withRSA. Você pode verificar usando sua chave pública da seguinte maneira:

#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/sha.h>

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

// Decodes a Base64 string into a vector of bytes using OpenSSL BIOs.
std::vector<unsigned char> base64_decode(const std::string& base64_string) {
    BIO *bio, *b64;
    b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    bio = BIO_new_mem_buf(base64_string.data(), base64_string.length());
    bio = BIO_push(b64, bio);

    std::vector<unsigned char> decoded_data;
    decoded_data.resize(base64_string.length());
    int length = BIO_read(bio, decoded_data.data(), decoded_data.size());
    if (length > 0) {
      decoded_data.resize(length);
    } else {
      decoded_data.clear();
    }
    BIO_free_all(bio);
    return decoded_data;
}

// Reads a PEM-encoded public key string and returns an EVP_PKEY object.
EVP_PKEY* createPublicKey(const std::string& publicKeyPem) {
  BIO* bio = BIO_new_mem_buf(publicKeyPem.data(), publicKeyPem.length());
  EVP_PKEY* pkey = PEM_read_bio_PUBKEY(bio, nullptr, nullptr, nullptr);
  BIO_free(bio);
  return pkey;
}

// Verifies the RSA-SHA1 signature of given data using a public key.
bool verifySignature(const std::string& publicKeyPem,
                     const std::string& originalData,
                     const std::string& signature_b64) {
  std::vector<unsigned char> signature = base64_decode(signature_b64);
  EVP_PKEY* pkey = createPublicKey(publicKeyPem);
  if (!pkey) {
    std::cerr << "Error loading public key." << std::endl;
    ERR_print_errors_fp(stderr);
    return false;
  }

  EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
  if (!mdctx) {
    std::cerr << "EVP_MD_CTX_new failed." << std::endl;
    EVP_PKEY_free(pkey);
    return false;
  }

  if (EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha1(), nullptr, pkey) <= 0 ||
      EVP_DigestVerifyUpdate(mdctx, originalData.c_str(),
                             originalData.length()) <= 0) {
    EVP_MD_CTX_free(mdctx);
    EVP_PKEY_free(pkey);
    std::cerr << "Error during EVP_DigestVerifyInit or EVP_DigestVerifyUpdate."
              << std::endl;
    return false;
  }

  int result = EVP_DigestVerifyFinal(
      mdctx, reinterpret_cast<const unsigned char*>(signature.data()),
      signature.size());

  EVP_MD_CTX_free(mdctx);
  EVP_PKEY_free(pkey);

  if (result == 0) {
    std::cerr << "Signature verification failed." << std::endl;
    return false;
  } else if (result != 1) {
    std::cerr << "Error during signature verification." << std::endl;
    ERR_print_errors_fp(stderr);
    return false;
  }

  return true;
}

Etapa 5: testar a integração

Agora está tudo pronto para testar sua integração com o Google Play Faturamento. Para testar durante a fase de desenvolvimento, recomendamos o uso de testadores de licença. Os testadores de licença têm acesso a pagamentos de teste que evitam a cobrança de dinheiro real pelas compras.

Para instruções sobre como configurar testadores de licença e um pacote de testes manuais que recomendamos, consulte a documentação sobre como testar a integração da Biblioteca Google Play Faturamento.