Google Play Billing Library für native PC-Spiele in Ihre App einbinden

Sie können mit Play Billing digitale Produkte in Ihrem Spiel verkaufen und so Einnahmen erzielen. Das SDK bietet APIs, mit denen Sie verfügbare Produkte anzeigen, den Kaufvorgang starten und Käufe verarbeiten können. Aufrufe dieser Abrechnungs-APIs werden mit dem Google-Konto ausgeführt, mit dem das Spiel im Google Play Spiele-Client gestartet wurde. Es sind keine zusätzlichen Anmeldeschritte erforderlich.

Wenn Sie die Android Play Billing Library eingebunden haben, sollten Ihnen diese Play Billing APIs bekannt sein. Alle serverseitigen Einbindungen in Play Billing können für PC-Titel wiederverwendet werden, da sie für Android und PC gleich sind.

Vorbereitung

Schritt 1: BillingClient erstellen

Seit 26.3.312.0

Verwenden Sie für SDK-Version 26.3.312.0 und höher BillingClientParameters, um einen BillingClient zu konfigurieren und zu instanziieren. So können Sie bei der Initialisierung bestimmte Funktionen aktivieren, z. B. ausstehende Käufe.

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

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

Vor 26.3.312.0

In SDK-Versionen vor 26.3.312.0 unterstützt der BillingClient nur die Instanziierung mit dem Standardkonstruktor. Erweiterte Konfigurationsoptionen sind in diesen Versionen nicht verfügbar. .

BillingClient billing_client;

Schritt 2: Nach früheren Käufen und Käufen suchen, die außerhalb Ihrer Anwendung abgeschlossen wurden

Wenn Ihre Anwendung gestartet wird oder wieder in den Vordergrund tritt, suchen Sie nach Käufen. Dies ist erforderlich, um Käufe zu erkennen, die außerhalb Ihres Spiels getätigt wurden, oder um den Zugriff auf Käufe freizuschalten, die der Nutzer zuvor getätigt hat.

  1. Suchen Sie mit BillingClient::QueryPurchases nach Käufen.

  2. Fahren Sie fort, indem Sie die Käufe verarbeiten.

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

Schritt 3: Verfügbare Produkte anzeigen

Sie können jetzt nach Ihren verfügbaren Produkten suchen und sie Ihren Nutzern präsentieren. Die Abfrage von Produktdetails ist ein wichtiger Schritt, bevor Sie Ihren Nutzern Produkte präsentieren, da dadurch lokalisierte Produktinformationen zurückgegeben werden.

Bevor Sie ein Produkt zum Verkauf anbieten, prüfen Sie, ob der Nutzer es bereits besitzt. Wenn der Nutzer ein Verbrauchsgut hat, das sich noch in seinem Kaufverlauf befindet, müssen Sie das Produkt verbrauchen, bevor er es noch einmal kaufen kann.

  1. Suchen Sie mit BillingClient::QueryProductDetails nach Produktdetails. Übergeben Sie die Produkt-IDs, die Sie in der Google Play Console registriert haben.
  2. Rendern Sie die ProductDetails, die den lokalisierten Namen und den Angebotspreis des Produkts enthalten.
  3. Behalten Sie einen Verweis auf das offer_token des Produkts bei. Damit wird ein Kaufvorgang für das Angebot gestartet.
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
}

Schritt 4: Kaufvorgang starten

Wenn der Nutzer Interesse an einem von Ihnen präsentierten Produkt zeigt, können Sie den Kaufvorgang starten.

Der Kaufvorgang wird im Spiel über ein nahtloses, WebView-basiertes Overlay abgeschlossen:

Nahtlose In-Game-Kaufabwicklung
Nahtloser Bezahlvorgang im Spiel
  1. Rufen Sie zuerst BillingClient::LaunchPurchaseFlow() auf. Übergeben Sie das offer_token, das Sie beim Abfragen der Produktdetails erhalten haben.
  2. Sobald der Kauf abgeschlossen ist, wird die Fortsetzungsfunktion mit dem Ergebnis aufgerufen.
  3. Im Erfolgsfall enthält die Fortsetzung ein ProductPurchaseDetails. Fahren Sie fort, indem Sie den Kauf verarbeiten.
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
}

Schritt 5: Kauf verarbeiten

Mit einem Backend-Server verarbeiten

Bei Spielen mit einem Backend-Server senden Sie das purchase_token an Ihren Backend-Server, um die Verarbeitung abzuschließen. Schließen Sie die restliche Verarbeitung mit den serverseitigen Play Billing APIs ab. Diese serverseitige Einbindung ist dieselbe wie bei einem Android-Spiel, das in Play Billing eingebunden ist.

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
}

Ohne Backend-Server verarbeiten

  1. Prüfen Sie, ob die Zahlung des Nutzers aussteht, indem Sie prüfen, ob ProductPurchaseDetails::purchase_state gleich PurchaseState::kPurchaseStatePurchased ist. Wenn der Kaufstatus „Ausstehend“ ist, benachrichtigen Sie den Nutzer, dass er zusätzliche Schritte ausführen muss, bevor er das gekaufte Produkt erhält.

  2. Gewähren Sie dem Nutzer Zugriff auf das gekaufte Produkt und aktualisieren Sie den Berechtigungsspeicher Ihres Spiels.

  3. Bei nicht verbrauchbaren Käufen (Produkte, die nur einmal gekauft werden können) prüfen Sie mit ProductPurchaseDetails::is_acknowledged, ob der Kauf bereits bestätigt wurde.

    1. Wenn der Kauf noch nicht bestätigt wurde, benachrichtigen Sie Google, dass dem Nutzer eine Berechtigung für das Produkt gewährt wird, indem Sie BillingClient::AcknowledgePurchase aufrufen.
  4. Bei verbrauchbaren Käufen (Produkte, die mehrmals gekauft werden können) benachrichtigen Sie Google, dass dem Nutzer eine Berechtigung für das Produkt gewährt wird, indem Sie BillingClient::ConsumePurchase aufrufen.

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

Clientseitige Bestätigung von Käufen

Die clientseitige Bestätigung bietet eine zusätzliche Sicherheitsebene, da die Kaufsignatur direkt in Ihrem Spielclient bestätigt wird:

Architektur der clientseitigen Signaturbestätigung von Käufen
Architektur der clientseitigen Bestätigung von Käufen

Sie erhalten die signature von ProductPurchaseDetails. Das Feld signature ist mit Ihrem privaten Schlüssel und dem Signaturalgorithmus SHA1withRSA signiert. Sie können die Bestätigung mit Ihrem öffentlichen Schlüssel so durchführen:

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

Schritt 6: Integration testen

Sie können jetzt Ihre Einbindung in Play Billing testen. Für Tests während der Entwicklungsphase empfehlen wir die Verwendung von Lizenztestern. Lizenztester haben Zugriff auf Testzahlungen, bei denen für Käufe kein echtes Geld abgebucht wird.

Eine Anleitung zum Einrichten von Lizenztestern und eine Reihe manueller Tests die wir empfehlen, finden Sie in der Dokumentation zum Testen der Einbindung der Google Play Billing Library.