Play 請求サービスを使用してデジタル アイテムを販売することで、ゲームを収益化できます。この SDK は、購入可能なアイテムを表示し、購入フローを開始して、購入を処理するための API を提供します。これらの課金 API の呼び出しは、Google Play Games クライアント内でゲームを起動した Google アカウントを使用して行われ、追加のログイン手順は必要ありません。
Android Play Billing ライブラリと統合している場合は、これらの Play Billing API は見慣れたもののはずです。Play Billing とのサーバーサイドの統合は、Android と PC で同じであるため、PC タイトルで再利用できます。
前提条件
SDK の設定を完了します。
Google Play の課金システムの概要を確認します。
Google Play のお支払い情報の設定を完了します。
ステップ 1: 以前の購入とアプリ外で完了した購入をクエリする
アプリの起動時やフォアグラウンドに戻ったときに、購入をクエリします。これは、ゲーム外で行われた購入を検出したり、ユーザーが以前に行った購入へのアクセス権を付与したりするために必要です。
BillingClient::QueryPurchasesを使用して購入をクエリします。購入を処理して続行します。
// 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
}
ステップ 2: 購入可能な商品を表示する
購入可能なアイテムをクエリしてユーザーに表示できます。商品詳細のクエリは、ローカライズされた商品情報を返すので、ユーザーにアイテムを表示する前の重要なステップとなります。
販売するアイテムを提示する前に、ユーザーがまだそのアイテムを所有していないことを確認します。ユーザーの購入履歴に消費型アイテムがまだある場合、ユーザーはそのアイテムを消費しない限り再購入できません。
BillingClient::QueryProductDetailsを使用してアイテムの詳細をクエリします。Google Play Console で登録したアイテム ID を渡します。- 商品のローカライズされた名前と価格を含む
ProductDetailsをレンダリングします。 - 商品の
offer_tokenへの参照を保持します。これは、特典の購入フローを開始するために使用されます。
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
}
ステップ 3: 購入フローを開始する
ユーザーが商品を購入する意思を示したら、購入フローを開始する準備ができていることをユーザーに伝えます。
- まず、
BillingClient::LaunchPurchaseFlow()を呼び出します。商品詳細のクエリ時に取得したoffer_tokenを渡します。 - 購入が完了すると、結果とともに継続関数が呼び出されます。
- 成功した場合、継続には
ProductPurchaseDetailsが含まれます。購入を処理して続行します。
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
}
ステップ 4: 購入手続きを行う
バックエンド サーバーを使用したプロセス
バックエンド サーバーがあるゲームの場合は、purchase_token をバックエンド サーバーに送信して処理を完了します。サーバーサイドの Play 請求サービス API を使用して、残りの処理を完了します。このサーバーサイドの統合は、Play Billing と統合された Android ゲームで行われるものと同じです。
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
}
バックエンド サーバーなしで処理する
ProductPurchaseDetails::purchase_stateがPurchaseState::kPurchaseStatePurchasedであることを確認して、ユーザーの支払いが保留になっていないことを確認します。購入状態が保留中の場合は、購入した商品を受け取る前に追加の手順を完了する必要があることをユーザーに通知します。購入したアイテムへのアクセス権をユーザーに付与し、ゲームの利用資格ストレージを更新します。
非消費型アイテム(一度しか購入できないアイテム)の購入については、
ProductPurchaseDetails::is_acknowledgedを使用して、購入がすでに承認されているかどうかを確認します。- 購入が確認されていない場合は、
BillingClient::AcknowledgePurchaseを呼び出して、ユーザーにプロダクトの利用資格が付与されることを Google に通知します。
- 購入が確認されていない場合は、
消費可能アイテム(複数回購入できるアイテム)の購入については、
BillingClient::ConsumePurchaseを呼び出して、ユーザーにアイテムの利用資格が付与されたことを Google に通知します。
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;
}
クライアントサイドの購入の検証
ProductPurchaseDetails から signature を取得します。signature フィールドは、SHA1withRSA 署名アルゴリズムを使用して秘密鍵で署名されます。公開鍵を使用して次のように検証できます。
#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;
}
ステップ 5: 統合をテストする
これで、Google Play 請求サービスとの統合をテストする準備が整いました。開発段階でテストするには、「ライセンス テスター」を利用することをおすすめします。ライセンス テスターは、購入に対して実際の課金が行われないテスト用支払い方法を利用できます。
ライセンス テスターと、推奨される一連の手動テストを設定する手順については、Google Play Billing Library 統合をテストする方法に関するドキュメントをご覧ください。