Monetisasi game Anda dengan menjual produk digital menggunakan Layanan Penagihan Play. SDK menawarkan API untuk menampilkan produk yang tersedia untuk dibeli, meluncurkan alur pembelian, dan memproses pembelian. Panggilan ke API penagihan ini dilakukan menggunakan Akun Google yang meluncurkan game di dalam klien Google Play Game dan tidak memerlukan langkah login tambahan.
Jika Anda telah terintegrasi dengan library Layanan Penagihan Play Android, API Layanan Penagihan Play ini akan terlihat familiar. Setiap integrasi sisi server dengan Layanan Penagihan Play dapat digunakan kembali oleh judul PC karena sama di Android &PC.
Prasyarat
Selesaikan penyiapan SDK.
Baca ringkasan sistem penagihan Google Play.
Selesaikan penyiapan Layanan Penagihan Play.
Langkah 1: Buat BillingClient
Sejak 26.3.312.0
Untuk SDK versi 26.3.312.0 dan yang lebih baru, gunakan
BillingClientParameters untuk mengonfigurasi dan
membuat instance BillingClient. Hal ini memungkinkan Anda mengaktifkan fitur tertentu selama inisialisasi, seperti pembelian tertunda.
// Set up initialization parameters
BillingClientParams params;
params.enable_pending_purchases = true;
// Instantiate the BillingClient with parameters
BillingClient billing_client(params);
Sebelum 26.3.312.0
Pada versi SDK sebelum 26.3.312.0, BillingClient
hanya mendukung pembuatan instance menggunakan konstruktor default. Opsi konfigurasi lanjutan tidak tersedia dalam versi ini.
.
BillingClient billing_client;
Langkah 2: Buat kueri untuk pembelian sebelumnya & pembelian yang diselesaikan di luar aplikasi Anda
Saat aplikasi Anda dimulai atau saat aplikasi kembali ke latar depan, buat kueri untuk pembelian. Hal ini diperlukan untuk mendeteksi pembelian yang terjadi di luar game Anda atau untuk membuka akses ke pembelian yang sebelumnya dilakukan oleh pengguna.
Buat kueri untuk pembelian menggunakan
BillingClient::QueryPurchases.Lanjutkan dengan memproses pembelian.
// 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
}
Langkah 3: Tampilkan produk yang tersedia untuk dibeli
Anda siap membuat kueri untuk produk yang tersedia dan menampilkannya kepada pengguna. Kueri untuk detail produk merupakan langkah penting sebelum menampilkan produk kepada pengguna, karena proses ini akan menampilkan informasi produk yang dilokalkan.
Sebelum menawarkan produk untuk dijual, pastikan pengguna belum memiliki produk tersebut. Jika pengguna memiliki item habis pakai yang masih ada dalam histori pembeliannya, Anda harus menggunakan produk tersebut sebelum pengguna dapat membelinya lagi.
- Buat kueri untuk detail produk menggunakan
BillingClient::QueryProductDetails. Teruskan ID produk yang Anda daftarkan di dalam Konsol Google Play. - Render the
ProductDetailsyang mencakup nama produk yang dilokalkan & harga penawaran. - Simpan referensi ke
offer_tokenproduk. Data ini digunakan untuk meluncurkan alur pembelian penawaran.
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
}
Langkah 4: Luncurkan alur pembelian
Saat pengguna menunjukkan niat untuk membeli produk yang telah Anda tampilkan, Anda siap meluncurkan alur pembelian.
- Mulai dengan memanggil
BillingClient::LaunchPurchaseFlow(). Teruskanoffer_tokenyang diperoleh saat membuat kueri detail produk. - Setelah pembelian selesai, fungsi kelanjutan akan dipanggil dengan hasilnya.
- Jika berhasil, kelanjutan akan berisi
ProductPurchaseDetails. Lanjutkan dengan memproses pembelian.
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
}
Langkah 5: Proses pembelian
Proses dengan server backend
Untuk game dengan server backend, selesaikan pemrosesan dengan mengirimkan
purchase_token ke server backend Anda. Selesaikan
sisa pemrosesan menggunakan
API Layanan Penagihan Play sisi server. Integrasi sisi server ini sama dengan yang dilakukan untuk game Android yang telah terintegrasi dengan Layanan Penagihan Play.
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
}
Proses tanpa server backend
Pastikan pembayaran pengguna tidak tertunda dengan memeriksa
ProductPurchaseDetails::purchase_stateadalahPurchaseState::kPurchaseStatePurchased. Jika status pembelian tertunda, beri tahu pengguna bahwa mereka harus menyelesaikan langkah tambahan sebelum dapat menerima produk yang dibeli.Berikan akses pengguna ke produk yang dibeli & perbarui penyimpanan hak game Anda.
Untuk pembelian tidak habis pakai (produk yang hanya dapat dibeli satu kali) periksa apakah pembelian telah dikonfirmasi menggunakan
ProductPurchaseDetails::is_acknowledged.- Jika pembelian belum dikonfirmasi, beri tahu Google bahwa pengguna sedang
diberi hak atas produk tersebut dengan memanggil
BillingClient::AcknowledgePurchase.
- Jika pembelian belum dikonfirmasi, beri tahu Google bahwa pengguna sedang
diberi hak atas produk tersebut dengan memanggil
Untuk pembelian habis pakai (produk yang dapat dibeli lebih dari satu kali) beri tahu Google bahwa pengguna diberi hak atas produk tersebut dengan memanggil
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;
}
Verifikasi pembelian sisi klien
Anda akan mendapatkan signature dari
ProductPurchaseDetails. Kolom signature ditandatangani dengan kunci pribadi Anda menggunakan algoritma tanda tangan SHA1withRSA. Anda dapat melakukan verifikasi menggunakan kunci publik Anda sebagai berikut:
#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;
}
Langkah 6: Uji integrasi Anda
Anda kini siap menguji integrasi dengan Layanan Penagihan Play. Untuk melakukan pengujian selama fase pengembangan, sebaiknya manfaatkan penguji lisensi. Penguji lisensi memiliki akses ke pembayaran pengujian sehingga penguji tidak akan dikenai biaya ketika melakukan pembelian.
Untuk mengetahui petunjuk cara menyiapkan penguji lisensi dan rangkaian pengujian manual yang sebaiknya Anda lakukan, lihat dokumentasi tentang cara menguji integrasi Library Layanan Penagihan Google Play.