Intégrer la bibliothèque Google Play Billing pour PC natif à votre application

Monétisez votre jeu en vendant des produits numériques à l'aide de Play Billing. Le SDK propose des API pour afficher les produits disponibles à l'achat, lancer le parcours d'achat et traiter les achats. Les appels à ces API de facturation sont effectués à l'aide du compte Google qui a lancé le jeu dans le client Google Play Jeux et ne nécessitent aucune étape de connexion supplémentaire.

Si vous avez intégré la bibliothèque Android Play Billing, ces API Play Billing devraient vous être familières. Toutes les intégrations côté serveur avec Play Billing peuvent être réutilisées par les titres PC, car elles sont identiques sur Android et PC.

Prérequis

  • Terminez la configuration du SDK.

  • Consultez la présentation du système de facturation de Google Play.

  • Terminez la configuration de Play Billing.

Étape 1 : Créez BillingClient

Depuis la version 26.3.312.0

Pour la version 26.3.312.0 du SDK et les versions ultérieures, utilisez BillingClientParameters pour configurer et instancier un BillingClient. Cela vous permet d'activer des fonctionnalités spécifiques lors de l'initialisation, comme les achats en attente.

// Set up initialization parameters
BillingClientParams params;
params.enable_pending_purchases = true;

// Instantiate the BillingClient with parameters
BillingClient billing_client(params);

Avant la version 26.3.312.0

Dans les versions du SDK antérieures à 26.3.312.0, le BillingClient n'est compatible qu'avec l'instanciation à l'aide du constructeur par défaut. Les options de configuration avancées ne sont pas disponibles dans ces versions. .

BillingClient billing_client;

Étape 2 : Interrogez les achats précédents et les achats effectués en dehors de votre application

Lorsque votre application démarre ou qu'elle revient au premier plan, interrogez les achats. Cela est nécessaire pour détecter les achats effectués en dehors de votre jeu ou pour déverrouiller l'accès aux achats effectués précédemment par l'utilisateur.

  1. Interrogez les achats à l'aide de BillingClient::QueryPurchases.

  2. Poursuivez le traitement des achats.

// 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
}

Étape 3 : Affichez les produits disponibles à l'achat

Vous pouvez interroger les produits disponibles et les présenter à vos utilisateurs. L'interrogation des informations détaillées sur un produit est une étape importante avant de présenter vos produits aux utilisateurs, car elle renvoie des informations localisées sur le produit.

Avant de proposer un produit à la vente, vérifiez que l'utilisateur n'en est pas déjà le propriétaire. Si l'utilisateur dispose d'un consommable qui se trouve toujours dans son historique d'achats, vous devez le consommer avant qu'il puisse l'acheter à nouveau.

  1. Interrogez les informations détaillées sur le produit à l'aide de BillingClient::QueryProductDetails. Transmettez les ID de produit que vous avez enregistrés dans la Google Play Console.
  2. Affichez les ProductDetails, qui incluent le nom localisé du produit et le prix de l’offre.
  3. Conservez une référence au offer_token du produit. Il est utilisé pour lancer un parcours d'achat pour l'offre.
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
}

Étape 4 : Lancez un parcours d'achat

Lorsque l'utilisateur indique qu'il souhaite acheter un produit que vous lui avez présenté, vous pouvez lancer le parcours d'achat.

  1. Commencez par appeler BillingClient::LaunchPurchaseFlow(). Transmettez le offer_token obtenu lors de l'interrogation des informations détaillées sur le produit.
  2. Une fois l'achat effectué, la fonction de continuation est appelée avec le résultat.
  3. Si l'opération réussit, la continuation contient un ProductPurchaseDetails. Poursuivez le traitement de l'achat.
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
}

Étape 5 : Traitez un achat

Traitement avec un serveur backend

Pour les jeux avec un serveur backend, terminez le traitement en envoyant le purchase_token à votre serveur backend. Terminez le reste du traitement à l'aide des API Play Billing côté serveur. Cette intégration côté serveur est la même que celle effectuée pour un jeu Android qui a été intégré à Play Billing.

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
}

Traitement sans serveur backend

  1. Assurez-vous que le paiement de l'utilisateur n'est pas en attente en vérifiant ProductPurchaseDetails::purchase_state est PurchaseState::kPurchaseStatePurchased. Si l'état de l'achat est en attente, informez l'utilisateur qu'il doit effectuer des étapes supplémentaires avant de pouvoir recevoir le produit acheté.

  2. Accordez à l'utilisateur l'accès au produit acheté et mettez à jour le stockage des droits d'accès de votre jeu.

  3. Pour les achats non consommables (produits qui ne peuvent être achetés qu'une seule fois) vérifiez si l'achat a déjà été confirmé à l'aide de ProductPurchaseDetails::is_acknowledged.

    1. Si l'achat n'a pas été confirmé, informez Google que l'utilisateur reçoit un droit d'accès au produit en appelant BillingClient::AcknowledgePurchase.
  4. Pour les achats consommables (produits qui peuvent être achetés plusieurs fois) informez Google que l'utilisateur reçoit un droit d'accès au produit en appelant 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;
}

Vérification des achats côté client

Vous obtenez le signature à partir de ProductPurchaseDetails. Le champ signature est signé avec votre clé privée à l'aide de l'algorithme de signature SHA1withRSA. Vous pouvez effectuer la validation à l'aide de votre clé publique comme suit :

#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;
}

Étape 6 : Testez votre intégration

Vous êtes maintenant prêt à tester votre intégration avec Play Billing. Pour effectuer des tests pendant la phase de développement, nous vous recommandons d'utiliser des testeurs de licence. Les testeurs de licence ont accès à des paiements test qui leur évitent d'avoir à payer réellement les achats.

Pour obtenir des instructions sur la configuration des testeurs de licence et une suite de tests manuels que nous vous recommandons d'effectuer, consultez la documentation sur la façon de tester votre intégration de la bibliothèque Google Play Billing.