يمكنك تحقيق الربح من لعبتك من خلال بيع المنتجات الرقمية باستخدام خدمة الفوترة في Play. توفّر حزمة تطوير البرامج (SDK) واجهات برمجة تطبيقات لعرض المنتجات المتاحة للشراء وبدء مسار الشراء ومعالجة عمليات الشراء. يتم إجراء طلبات إلى واجهات برمجة التطبيقات هذه باستخدام حساب Google الذي بدأ اللعبة داخل عميل "ألعاب Google Play"، ولا تتطلّب أي خطوات إضافية لتسجيل الدخول.
إذا كنت قد نفّذت عملية التكامل مع مكتبة الفوترة في Play في Android، من المفترض أن تكون واجهات برمجة التطبيقات هذه مألوفة لديك. يمكن إعادة استخدام أي عمليات تكامل من جهة الخادم مع خدمة "الفوترة في Play" في ألعاب الكمبيوتر لأنّها متطابقة على كلٍّ من أجهزة Android والكمبيوتر.
المتطلبات الأساسية
أكمِل إعداد حزمة تطوير البرامج (SDK).
اطّلِع على نظرة عامة على نظام الفوترة في Google Play.
أكمِل إعداد خدمة "الفوترة في Play".
الخطوة 1: إنشاء BillingClient
الإصدار 26.3.312.0 والإصدارات الأحدث
بالنسبة إلى الإصدار 26.3.312.0 والإصدارات الأحدث من حزمة تطوير البرامج (SDK)، استخدِم
BillingClientParameters لإعداد BillingClient وإنشائه. يسمح لك ذلك بتفعيل ميزات محدّدة أثناء عملية التهيئة، مثل عمليات الشراء المعلقة.
// Set up initialization parameters
BillingClientParams params;
params.enable_pending_purchases = true;
// Instantiate the BillingClient with parameters
BillingClient billing_client(params);
الإصدارات الأقدم من 26.3.312.0
في الإصدارات الأقدم من 26.3.312.0 من حزمة تطوير البرامج (SDK)، لا يتيح BillingClient سوى إنشاء مثيل باستخدام أداة الإنشاء التلقائية. لا تتوفّر خيارات الإعداد المتقدّمة في هذه الإصدارات.
.
BillingClient billing_client;
الخطوة 2: طلب عمليات الشراء السابقة وعمليات الشراء التي تمّت خارج تطبيقك
عند بدء تشغيل تطبيقك أو عند إعادة ظهوره في المقدّمة، اطلب عمليات الشراء. هذا ضروري لرصد عمليات الشراء التي تمّت خارج لعبتك أو لإتاحة الوصول إلى عمليات الشراء التي سبق أن أجراها المستخدم.
اطلب عمليات الشراء باستخدام
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
}
الخطوة 3: عرض المنتجات المتاحة للشراء
أنت الآن جاهز لطلب منتجاتك المتاحة وعرضها على المستخدمين. يُعدّ طلب تفاصيل المنتج خطوة مهمة قبل عرض منتجاتك على المستخدمين، لأنّه يعرض معلومات المنتج المترجَمة.
قبل عرض منتج للبيع، تأكَّد من أنّ المستخدم لا يملك المنتج حاليًا. إذا كان لدى المستخدم منتج استهلاكي لا يزال في سجلّ الشراء، عليك استهلاك المنتج قبل أن يتمكّن من شرائه مرة أخرى.
- اطلب تفاصيل المنتج باستخدام
BillingClient::QueryProductDetails. مرِّر أرقام تعريف المنتجات التي سجّلتها داخل Google Play Console. - اعرض
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
}
الخطوة 4: بدء مسار الشراء
عندما يُظهر المستخدم نيّة شراء منتج عرضته عليه، تكون جاهزًا لبدء مسار الشراء.
- ابدأ باستدعاء
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
}
الخطوة 5: معالجة عملية الشراء
المعالجة باستخدام خادم الخلفية
بالنسبة إلى الألعاب التي تتضمّن خادم خلفية، أكمِل عملية المعالجة من خلال إرسال الـ
purchase_token إلى خادم الخلفية. أكمِل باقي عملية المعالجة باستخدام
واجهات برمجة التطبيقات من جهة الخادم لخدمة "الفوترة في Play". يتطابق هذا التكامل من جهة الخادم مع التكامل الذي يتم إجراؤه للعبة Android التي تم دمجها مع خدمة "الفوترة في 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
}
المعالجة بدون خادم الخلفية
تأكَّد من أنّ دفعة المستخدم ليست معلّقة من خلال التحقّق من أنّ
ProductPurchaseDetails::purchase_stateهيPurchaseState::kPurchaseStatePurchased. إذا كانت حالة الشراء معلّقة، أخبِر المستخدم بأنّه بحاجة إلى إكمال خطوات إضافية قبل أن يتمكّن من تلقّي المنتج الذي اشتراه.امنح المستخدم إذن الوصول إلى المنتج الذي اشتراه وعدِّل مساحة تخزين أذونات الاستخدام في لعبتك.
بالنسبة إلى عمليات الشراء غير الاستهلاكية (المنتجات التي يمكن شراؤها مرة واحدة فقط) تحقَّق مما إذا تم الإقرار بعملية الشراء باستخدام
ProductPurchaseDetails::is_acknowledged.- إذا لم يتم الإقرار بعملية الشراء، أخبِر Google بأنّه يتم منح المستخدم إذن استخدام المنتج من خلال استدعاء
BillingClient::AcknowledgePurchase.
- إذا لم يتم الإقرار بعملية الشراء، أخبِر Google بأنّه يتم منح المستخدم إذن استخدام المنتج من خلال استدعاء
بالنسبة إلى عمليات الشراء الاستهلاكية (المنتجات التي يمكن شراؤها أكثر من مرة) أخبِر Google بأنّه يتم منح المستخدم إذن استخدام المنتج من خلال استدعاء
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;
}
التحقق من عمليات الشراء من جهة العميل
يمكنك الحصول على signature من
ProductPurchaseDetails. يتم توقيع الحقل 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;
}
الخطوة 6: اختبار عملية التكامل
أنت الآن جاهز لاختبار عملية التكامل مع خدمة "الفوترة في Play". لإجراء الاختبار أثناء مرحلة التطوير، ننصحك باستخدام مختبِري التراخيص. يمكن لمختبِري التراخيص الوصول إلى عمليات الدفع التجريبية التي تتجنّب تحصيل أموال حقيقية مقابل عمليات الشراء.
للحصول على تعليمات حول كيفية إعداد مختبِري التراخيص ومجموعة من الاختبارات اليدوية التي ننصحك بإجرائها، اطّلِع على المستندات حول كيفية اختبار عملية تكامل Google Play Billing Library.