במאמר הזה מוסבר איך לשלב את ספריית החיובים ב-Google Play באפליקציה כדי להתחיל למכור מוצרים.
מחזור החיים של רכישה
כאן מתואר תהליך רכישה אופייני של רכישה חד-פעמית או של מינוי.
- הציגו למשתמשים מה הם יכולים לקנות.
- מפעילים את תהליך הרכישה כדי שהמשתמש יאשר את הרכישה.
- מאמתים את הרכישה בשרת.
- לתת תוכן למשתמש.
- לאשר את קבלת התוכן. במוצרים מתכלים, צריך לצרוך את הרכישה כדי שהמשתמש יוכל לקנות את הפריט שוב.
המינויים מתחדשים באופן אוטומטי עד שמבטלים אותם. מינוי יכול לעבור בין המצבים הבאים:
- פעיל: המשתמש מנהל את החשבון באופן תקין ויש לו גישה למינוי.
- בוטל: המשתמש ביטל את המינוי אבל עדיין יש לו גישה עד לתאריך התפוגה.
- בתקופת חסד: המשתמש נתקל בבעיה בתשלום אבל עדיין יש לו גישה בזמן ש-Google מנסה שוב לחייב את אמצעי התשלום.
- בהמתנה: למשתמש הייתה בעיה בתשלום ואין לו יותר גישה בזמן ש-Google מנסה שוב לחייב את אמצעי התשלום.
- מושהה: המשתמש השהה את הגישה שלו, ואין לו גישה עד שהוא יפעיל אותה מחדש.
- פג תוקף: המשתמש ביטל את המינוי ואיבד את הגישה אליו. המשתמש נחשב כנוטש בתאריך התפוגה.
הפעלת חיבור ל-Google Play
השלב הראשון בשילוב עם מערכת החיוב של Google Play הוא להוסיף את ספריית החיובים ב-Google Play לאפליקציה ולהפעיל חיבור.
הוספת התלות של ספריית החיובים ב-Google Play
מוסיפים את התלות בספריית החיובים ב-Google Play לקובץ build.gradle של האפליקציה, כמו שמוצג כאן:
Groovy
dependencies { def billing_version = "9.1.0" implementation "com.android.billingclient:billing:$billing_version" }
Kotlin
dependencies { val billing_version = "9.1.0" implementation("com.android.billingclient:billing:$billing_version") }
אם אתם משתמשים ב-Kotlin, מודול KTX של ספריית החיובים ב-Google Play מכיל תוספים של Kotlin ותמיכה בשגרות המשך (coroutines) שמאפשרים לכם לכתוב ב-Kotlin אידיומטי כשאתם משתמשים בספריית החיובים ב-Google Play. כדי לכלול את התוספים האלה בפרויקט, מוסיפים את התלות הבאה לקובץ build.gradle של האפליקציה, כמו שמוצג:
Groovy
dependencies { def billing_version = "9.1.0" implementation "com.android.billingclient:billing-ktx:$billing_version" }
Kotlin
dependencies { val billing_version = "9.1.0" implementation("com.android.billingclient:billing-ktx:$billing_version") }
אתחול של BillingClient
אחרי שמוסיפים תלות בספריית החיובים ב-Google Play, צריך להפעיל מופע של BillingClient. BillingClient הוא הממשק הראשי לתקשורת בין ספריית החיובים ב-Google Play לבין שאר האפליקציה. BillingClient מספק שיטות נוחות, סינכרוניות ואסינכרוניות, להרבה פעולות חיוב נפוצות. חשוב לזכור:
- מומלץ שיהיה חיבור פעיל אחד של
BillingClientפתוח בכל פעם, כדי למנוע כמה קריאות חוזרות (callbacks) שלPurchasesUpdatedListenerלאירוע אחד. - מומלץ ליזום חיבור ל-BillingClient כשהאפליקציה מופעלת או עוברת לחזית, כדי לוודא שהאפליקציה מעבדת רכישות בזמן. כדי לעשות את זה, אפשר להשתמש ב-
ActivityLifecycleCallbacksשרשום על ידיregisterActivityLifecycleCallbacksולהאזין ל-onActivityResumed כדי להפעיל חיבור כשמזהים לראשונה שפעילות מתחדשת. בקטע בנושא עיבוד רכישות מוסבר למה כדאי לפעול לפי השיטה המומלצת הזו. חשוב גם לזכור לסיים את החיבור כשהאפליקציה נסגרת.
כדי ליצור BillingClient, משתמשים ב-newBuilder. אפשר להעביר כל הקשר אל newBuilder(), וה-SDK של BillingClient משתמש בו כדי לקבל הקשר של אפליקציה. זה אומר שאתם לא צריכים לדאוג לגבי דליפות זיכרון. כדי לקבל עדכונים על רכישות, צריך גם להפעיל את setListener ולהעביר הפניה אל PurchasesUpdatedListener. המאזין הזה מקבל עדכונים על כל הרכישות באפליקציה.
Kotlin
private val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases -> // To be implemented in a later section. } private var billingClient = BillingClient.newBuilder(context) .setListener(purchasesUpdatedListener) // Configure other settings. .build()
Java
private PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
@Override
public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
// To be implemented in a later section.
}
};
private BillingClient billingClient = BillingClient.newBuilder(context)
.setListener(purchasesUpdatedListener)
// Configure other settings.
.build();
חיבור ל-Google Play
אחרי שיוצרים BillingClient, צריך ליצור חיבור ל-Google Play.
כדי להתחבר ל-Google Play, צריך להתקשר למספר startConnection. תהליך החיבור הוא אסינכרוני, וצריך להטמיע BillingClientStateListener כדי לקבל קריאה חוזרת (callback) כשהגדרת הלקוח מסתיימת והוא מוכן לשלוח בקשות נוספות.
אם לא הפעלתם חיבור מחדש אוטומטי לשירות, אתם צריכים גם להטמיע לוגיקה של ניסיון חוזר כדי לטפל בחיבורים שאבדו ל-Google Play. כדי להטמיע לוגיקה של ניסיונות חוזרים, צריך לשנות את שיטת הקריאה החוזרת (callback) onBillingServiceDisconnected() ולוודא שה-BillingClient קורא לשיטה startConnection() כדי להתחבר מחדש ל-Google Play לפני שליחת בקשות נוספות. אם הפעלתם את החיבור האוטומטי מחדש לשירות, אתם יכולים להטמיע את השיטה הזו כ-no-op.
בדוגמה הבאה מוסבר איך להתחיל חיבור ולבדוק שהוא מוכן לשימוש:
Kotlin
billingClient.startConnection(object : BillingClientStateListener { override fun onBillingSetupFinished(billingResult: BillingResult) { if (billingResult.responseCode == BillingResponseCode.OK) { // The BillingClient is ready. You can query purchases here. } } override fun onBillingServiceDisconnected() { // If automatic service reconnection is enabled, this can be left empty (no-op) // because the library handles retries. You can still use this for non-retry // tasks like logging or updating the UI to reflect a disconnected state. // Otherwise, try to restart the connection on the next request to // Google Play by calling the startConnection() method. } })
Java
billingClient.startConnection( new BillingClientStateListener() { @Override public void onBillingSetupFinished(BillingResult billingResult) { if (billingResult.getResponseCode() == BillingResponseCode.OK) { // The BillingClient is ready. You can query purchases here. // It's a good practice to query products after the connection is established. queryProductDetails(); } } @Override public void onBillingServiceDisconnected() { // Try to restart the connection on the next request to // Google Play by calling the startConnection() method. // This is automatically handled by the library when you call a method that requires a connection. } });
חיבור מחדש באופן אוטומטי
בגרסה 8.0.0 של BillingClient.Builder הוספנו את השיטה enableAutoServiceReconnection(), שמאפשרת לספריית החיובים ב-Play ליצור מחדש באופן אוטומטי את החיבור לשירות אם מתבצעת קריאה ל-API כשהשירות מנותק. זה יכול להוביל להפחתה בתגובות SERVICE_DISCONNECTED, כי החיבור מחדש מטופל באופן פנימי לפני שהקריאה ל-API מתבצעת.
איך מפעילים חיבור מחדש אוטומטי
כשיוצרים מכונה של BillingClient, משתמשים ב-method enableAutoServiceReconnection() ב-BillingClient.Builder כדי להפעיל חיבור מחדש אוטומטי.
Kotlin
val billingClient = BillingClient.newBuilder(context) .setListener(listener) .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()) .enableAutoServiceReconnection() // Add this line to enable reconnection .build()
Java
BillingClient billingClient = BillingClient.newBuilder(context)
.setListener(listener)
.enablePendingPurchases()
.enableAutoServiceReconnection() // Add this line to enable reconnection
.build();
הצגת מוצרים שזמינים לקנייה
אחרי שיוצרים חיבור ל-Google Play, אפשר לשלוח שאילתות לגבי המוצרים הזמינים ולהציג אותם למשתמשים.
שאילתה לקבלת פרטי המוצר היא שלב חשוב לפני הצגת המוצרים למשתמשים, כי היא מחזירה מידע על המוצר שמותאם למיקום. אם אתם מציעים מינויים, ודאו שדף המוצר עומד בכל דרישות המדיניות של Play.
כדי לשלוח שאילתה על פרטי מוצר בחיוב חד-פעמי, צריך להפעיל את ה-method queryProductDetailsAsync. השיטה הזו יכולה להחזיר כמה מבצעים, בהתאם להגדרות של המוצר בחיוב חד-פעמי. מידע נוסף זמין במאמר אפשרויות רכישה ומבצעים מרובים למוצרים בחיוב חד-פעמי.
כדי לטפל בתוצאה של הפעולה האסינכרונית, צריך גם לציין מאזין שמטמיע את הממשק ProductDetailsResponseListener.
אחר כך אפשר לשנות את onProductDetailsResponse, שמודיע למאזין כשהשאילתה מסתיימת, כמו בדוגמה הבאה:
Kotlin
val queryProductDetailsParams = QueryProductDetailsParams.newBuilder() .setProductList( listOf( Product.newBuilder() .setProductId("product_id_example") .setProductType(ProductType.SUBS) .build() ) ) .build() billingClient.queryProductDetailsAsync(queryProductDetailsParams) { billingResult, queryProductDetailsResult -> if (billingResult.responseCode == BillingResponseCode.OK) { for (productDetails in queryProductDetailsResult.productDetailsList) { // Process successfully retrieved product details here. } for (unfetchedProduct in queryProductDetailsResult.unfetchedProductList) { // Handle any unfetched products as appropriate. } } }
Java
QueryProductDetailsParams queryProductDetailsParams =
QueryProductDetailsParams.newBuilder()
.setProductList(
ImmutableList.of(
Product.newBuilder()
.setProductId("product_id_example")
.setProductType(ProductType.SUBS)
.build()))
.build();
billingClient.queryProductDetailsAsync(
queryProductDetailsParams,
new ProductDetailsResponseListener() {
public void onProductDetailsResponse(BillingResult billingResult,
QueryProductDetailsResult queryProductDetailsResult) {
if (billingResult.getResponseCode() == BillingResponseCode.OK) {
for (ProductDetails productDetails : queryProductDetailsResult.getProductDetailsList()) {
// Process successfully retrieved product details here.
}
for (UnfetchedProduct unfetchedProduct : queryProductDetailsResult.getUnfetchedProductList()) {
// Handle any unfetched products as appropriate.
}
}
}
}
)
כששולחים שאילתה לגבי פרטי המוצר, צריך להעביר מופע של QueryProductDetailsParams שמציין רשימה של מחרוזות של מזהי מוצרים שנוצרו ב-Google Play Console, יחד עם ProductType. הערך של ProductType יכול להיות ProductType.INAPP למוצרים בחיוב חד-פעמי או ProductType.SUBS למינויים.
שאילתה עם תוספי Kotlin
אם משתמשים בתוספים של Kotlin, אפשר לשלוח שאילתה לגבי פרטי מוצר בחיוב חד-פעמי באמצעות קריאה לפונקציית התוסף queryProductDetails().
queryProductDetails() משתמש ב-coroutines של Kotlin, כך שלא צריך להגדיר מאזין נפרד. במקום זאת, הפונקציה מושהית עד שהשאילתה מסתיימת, ואז אפשר לעבד את התוצאה:
suspend fun processPurchases() { val productList = listOf( QueryProductDetailsParams.Product.newBuilder() .setProductId("product_id_example") .setProductType(BillingClient.ProductType.SUBS) .build() ) val params = QueryProductDetailsParams.newBuilder() params.setProductList(productList) // leverage queryProductDetails Kotlin extension function val productDetailsResult = withContext(Dispatchers.IO) { billingClient.queryProductDetails(params.build()) } // Process the result. }
במקרים נדירים, מכשירים מסוימים לא תומכים ב-ProductDetails וב-queryProductDetailsAsync(), בדרך כלל בגלל גרסאות מיושנות של Google Play Services. כדי לספק תמיכה מתאימה בתרחיש הזה, כדאי לקרוא איך משתמשים בתכונות של תאימות לדורות קודמים במדריך להעברת נתונים (מיגרציה) של ספריית החיובים ב-Play בגרסה 7.
עיבוד התוצאה
ספריית החיובים ב-Google Play מאחסנת את תוצאות השאילתה באובייקט QueryProductDetailsResult. QueryProductDetailsResult מכיל List של אובייקטים מסוג ProductDetails. לאחר מכן, אפשר להפעיל מגוון שיטות על כל אובייקט ProductDetails ברשימה כדי להציג מידע רלוונטי על מוצר בחיוב חד-פעמי שאוחזר בהצלחה, כמו המחיר או התיאור שלו. כדי לראות את פרטי המוצר הזמינים, עיינו ברשימת השיטות במחלקה ProductDetails.
QueryProductDetailsResult מכיל גם List של אובייקטים מסוג UnfetchedProduct. אחר כך אפשר לשלוח שאילתה לכל UnfetchedProduct כדי לקבל קוד סטטוס שתואם לסיבה שבגללה השליפה נכשלה.
כדי לראות את פרטי המוצר הזמינים שלא אוחזרו, עיינו ברשימת השיטות במחלקה UnfetchedProduct.
לפני שמציעים פריט למכירה, צריך לבדוק אם הפריט כבר נמצא בבעלות המשתמש. אם למשתמש יש פריט מתכלה שעדיין נמצא בספריית הפריטים שלו, הוא צריך לצרוך את הפריט לפני שיוכל לקנות אותו שוב.
לפני שמציעים מינוי, צריך לוודא שהמשתמש לא רשום כבר. חשוב לשים לב גם לנקודות הבאות:
במינויים, השיטה
queryProductDetailsAsync()מחזירה את פרטי המוצר מסוג מינוי ועד 50 מבצעים שהמשתמש עומד בדרישות שלהם לכל מינוי. אם המשתמש מנסה לרכוש מבצע שהוא לא זכאי לו (לדוגמה, אם האפליקציה מציגה רשימה לא עדכנית של מבצעים שהמשתמש זכאי להם), המערכת של Play מודיעה למשתמש שהוא לא זכאי למבצע, והמשתמש יכול לבחור לרכוש את מינוי הבסיס במקום זאת.במוצרים בחיוב חד-פעמי, השיטה
queryProductDetailsAsync()מחזירה רק את המבצעים שהמשתמש עומד בדרישות שלהם. אם המשתמש מנסה לרכוש מבצע שהוא לא עומד בתנאים שלו (לדוגמה, אם המשתמש הגיע למגבלת כמות הרכישות), המערכת של Play מודיעה למשתמש שהוא לא עומד בתנאים, והמשתמש יכול לבחור לרכוש במקום זאת את המבצע של אפשרות הרכישה.
הפעלת תהליך הרכישה
כדי להתחיל בקשת רכישה מהאפליקציה, צריך להפעיל את השיטה launchBillingFlow() מה-thread הראשי של האפליקציה. השיטה הזו מקבלת הפניה לאובייקט BillingFlowParams שמכיל את האובייקט הרלוונטי ProductDetails שהתקבל מהקריאה ל-queryProductDetailsAsync. כדי ליצור אובייקט BillingFlowParams, משתמשים במחלקה BillingFlowParams.Builder.
Kotlin
// An activity reference from which the billing flow will be launched. val activity : Activity = ... val productDetailsParamsList = listOf( BillingFlowParams.ProductDetailsParams.newBuilder() // retrieve a value for productDetails by calling queryProductDetailsAsync() .setProductDetails(productDetails) // Get the offer token: // a. For one-time products, call ProductDetails.getOneTimePurchaseOfferDetailsList() // for a list of offers that are available to the user. // b. For subscriptions, call ProductDetails.getSubscriptionOfferDetails() // for a list of offers that are available to the user. .setOfferToken(selectedOfferToken) .build() ) val billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .build() // Launch the billing flow val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)
Java
// An activity reference from which the billing flow will be launched. Activity activity = ...; ImmutableList<BillingFlowParams.ProductDetailsParams> productDetailsParamsList = ImmutableList.of( BillingFlowParams.ProductDetailsParams.newBuilder() // retrieve a value for "productDetails" by calling queryProductDetailsAsync() .setProductDetails(productDetails) // Get the offer token: // a. For one-time products, call ProductDetails.getOneTimePurchaseOfferDetailsList() // for a list of offers that are available to the user. // b. For subscriptions, call ProductDetails.getSubscriptionOfferDetails() // for a list of offers that are available to the user. .setOfferToken(selectedOfferToken) .build() ); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .build(); // Launch the billing flow BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);
ה-method launchBillingFlow() מחזירה אחד מכמה קודי תגובה שמפורטים ב-BillingClient.BillingResponseCode. חשוב לבדוק את התוצאה הזו כדי לוודא שלא היו שגיאות בהפעלת תהליך הרכישה. אם הערך של BillingResponseCode הוא OK, סימן שההפעלה הצליחה.
אם הקריאה ל-launchBillingFlow() מצליחה, המערכת מציגה את מסך הרכישה של Google Play. באיור 1 מוצג מסך רכישה של מינוי:
Google Play קוראת ל-onPurchasesUpdated() כדי להעביר את התוצאה של פעולת הרכישה למאזין שמטמיע את הממשק PurchasesUpdatedListener. המאזין מצוין באמצעות השיטה setListener() כשמאתחלים את הלקוח.
צריך להטמיע את onPurchasesUpdated() כדי לטפל בקודי תגובה אפשריים. בדוגמה הבאה אפשר לראות איך לשנות את onPurchasesUpdated():
Kotlin
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) { if (billingResult.responseCode == BillingResponseCode.OK && purchases != null) { for (purchase in purchases) { // Process the purchase as described in the next section. } } else if (billingResult.responseCode == BillingResponseCode.USER_CANCELED) { // Handle an error caused by a user canceling the purchase flow. } else { // Handle any other error codes. } }
Java
@Override
void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
if (billingResult.getResponseCode() == BillingResponseCode.OK
&& purchases != null) {
for (Purchase purchase : purchases) {
// Process the purchase as described in the next section.
}
} else if (billingResult.getResponseCode() == BillingResponseCode.USER_CANCELED) {
// Handle an error caused by a user canceling the purchase flow.
} else {
// Handle any other error codes.
}
}
אם הרכישה בוצעה בהצלחה, יופיע מסך של Google Play שמאשר את הרכישה, כמו באיור 2.
רכישה מוצלחת יוצרת גם אסימון רכישה, שהוא מזהה ייחודי שמייצג את המשתמש ואת מזהה המוצר של המוצר בחיוב חד-פעמי שהמשתמש רכש. האפליקציות יכולות לאחסן את אסימון הרכישה באופן מקומי, אבל אנחנו ממליצים מאוד להעביר את האסימון לשרת בק-אנד מאובטח, שבו אפשר לאמת את הרכישה ולהגן מפני הונאה. התהליך הזה מתואר בפירוט במאמר זיהוי ועיבוד של רכישות.
המשתמש מקבל גם קבלה על העסקה באימייל, שכוללת את מזהה ההזמנה או מזהה ייחודי של העסקה. המשתמשים מקבלים אימייל עם מזהה הזמנה ייחודי לכל רכישה של מוצר בחיוב חד-פעמי, וגם לרכישה הראשונית של המינוי ולחידושים האוטומטיים הקבועים הבאים. אתם יכולים להשתמש במזהה ההזמנה כדי לנהל החזרים כספיים ב-Google Play Console.
ציון מחיר בהתאמה אישית
אם אתם יכולים להפיץ את האפליקציה למשתמשים באיחוד האירופי, אתם צריכים להשתמש ב-method setIsOfferPersonalized() כשאתם קוראים ל-launchBillingFlow כדי להודיע למשתמשים שהמחיר של הפריט הותאם אישית באמצעות קבלת החלטות אוטומטית.
צריך לעיין בסעיף 6 (1) (ea) של הדירקטיבה בנושא זכויות הצרכן 2011/83/EU כדי לקבוע אם המחיר שאתם מציעים למשתמשים מותאם אישית.
הפונקציה setIsOfferPersonalized() מקבלת קלט בוליאני. כשהאפליקציה true, ממשק המשתמש של Play כולל את הגילוי הנאות. כשהערך הוא false, ממשק המשתמש משמיט את הגילוי הנאות. ערך ברירת המחדל הוא false.
מידע נוסף זמין במרכז העזרה לצרכנים.
צירוף מזהי משתמשים
כשמפעילים את תהליך הרכישה, האפליקציה יכולה לצרף את כל מזהי המשתמשים שיש לכם עבור המשתמש שמבצע את הרכישה באמצעות obfuscatedAccountId או obfuscatedProfileId. דוגמה למזהה כזה יכולה להיות גרסה מוסתרת של פרטי ההתחברות של המשתמש במערכת שלכם. הגדרת הפרמטרים האלה יכולה לעזור ל-Google לזהות הונאות. בנוסף, הוא יכול לעזור לכם לוודא שהרכישות משויכות למשתמש הנכון, כפי שמוסבר בקטע הענקת הרשאות למשתמשים.
זיהוי ועיבוד של רכישות
הזיהוי והעיבוד של רכישה שמתוארים בקטע הזה רלוונטיים לכל סוגי הרכישות, כולל רכישות מחוץ לאפליקציה כמו מימוש מבצעים.
האפליקציה מזהה רכישות חדשות ורכישות ממתינות שהושלמו באחת מהדרכים הבאות:
- כשמתבצעת קריאה ל-
onPurchasesUpdatedכתוצאה מכך שהאפליקציה קוראת ל-launchBillingFlow(כפי שמתואר בסעיף הקודם), או אם האפליקציה פועלת עם חיבור פעיל לספריית החיובים כשמתבצעת רכישה מחוץ לאפליקציה או כשמושלמת רכישה ממתינה. לדוגמה, חבר/ה בקבוצה המשפחתית מאשר/ת רכישה ממתינה במכשיר אחר. - כשהאפליקציה מפעילה את queryPurchasesAsync כדי לשלוח שאילתה לגבי הרכישות של המשתמש.
במקרה הראשון, הפונקציה onPurchasesUpdated תופעל באופן אוטומטי לרכישות חדשות או לרכישות שהושלמו, כל עוד האפליקציה פועלת ויש לה חיבור פעיל לספריית החיובים ב-Google Play. אם האפליקציה לא פועלת או שאין לה חיבור פעיל לספריית החיובים ב-Google Play, הפונקציה onPurchasesUpdated לא תופעל. חשוב לזכור: מומלץ שהאפליקציה תנסה לשמור על חיבור פעיל כל עוד היא פועלת בחזית, כדי שהיא תקבל עדכונים על רכישות בזמן.
במקרה השני, צריך להפעיל את BillingClient.queryPurchasesAsync() כדי לוודא שהאפליקציה מעבדת את כל הרכישות. מומלץ לעשות את זה כשהאפליקציה יוצרת חיבור לספריית החיובים ב-Google Play (מומלץ לעשות את זה כשהאפליקציה מופעלת או עוברת לחזית, כמו שמתואר בקטע הפעלת BillingClient. כדי לעשות את זה, צריך להפעיל את queryPurchasesAsync כשמקבלים תוצאה מוצלחת ל-onServiceConnected. חשוב מאוד לפעול לפי ההמלצה הזו כדי לטפל באירועים ובמצבים כמו:
- בעיות ברשת במהלך הרכישה: משתמש יכול לבצע רכישה מוצלחת ולקבל אישור מ-Google, אבל המכשיר שלו מאבד את החיבור לרשת לפני שהמכשיר והאפליקציה שלכם מקבלים הודעה על הרכישה דרך
PurchasesUpdatedListener. - מכשירים מרובים: משתמש יכול לקנות פריט במכשיר אחד ולצפות לראות את הפריט כשהוא עובר למכשיר אחר.
- טיפול ברכישות שבוצעו מחוץ לאפליקציה: אפשר לבצע רכישות מסוימות מחוץ לאפליקציה, כמו מימוש מבצעים.
- טיפול במעברים בין מצבי רכישה: משתמש יכול להשלים תשלום על רכישה ממתינה כשהאפליקציה לא פועלת, ולצפות לקבל אישור על השלמת הרכישה כשהוא פותח את האפליקציה.
- מינויים מושעים: מינוי יכול להיות מושעה במהלך מחזור החיים של המינוי. הפונקציה BillingClient.queryPurchasesAsync() תחזיר מינויים מושעים רק אם הפרמטר
includeSuspendedSubscriptionsמוגדר ב-QueryPurchasesParams.Builder. מינויים מושעים לא מוחזרים ב-PurchasesUpdatedListener.
אחרי שהאפליקציה מזהה רכישה חדשה או רכישה שהושלמה, היא צריכה:
- מאמתים את הרכישה.
- מעניקים למשתמש גישה לתוכן של הרכישות שהושלמו.
- מודיעים למשתמש.
- להודיע ל-Google שהאפליקציה עיבדה רכישות שהושלמו.
השלבים האלה מפורטים בקטעים הבאים, ואחריהם יש קטע שמסכם את כל השלבים.
מאמתים את הרכישה
האפליקציה צריכה תמיד לאמת את הלגיטימיות של הרכישות לפני שהיא מעניקה הטבות למשתמש. כדי לעשות זאת, פועלים לפי ההנחיות שמפורטות במאמר אימות רכישות לפני הענקת הרשאות. רק אחרי אימות הרכישה, האפליקציה צריכה להמשיך לעבד את הרכישה ולהעניק הרשאות למשתמש, כפי שמתואר בסעיף הבא. אם רכישה נכשלת בבדיקות האימות, הזכאות או ניצול לרעה, עיינו במאמר טיפול ברכישות שנדחו או שלא מולאו.
הענקת הרשאה למשתמש
אחרי שהאפליקציה מאמתת את הרכישה, היא יכולה להמשיך להעניק את ההרשאה למשתמש ולהודיע לו על כך. לפני שמעניקים את ההרשאה, צריך לוודא שהאפליקציה בודקת שמצב הרכישה הוא PURCHASED. אם הרכישה נמצאת במצב PENDING, האפליקציה צריכה להודיע למשתמש שהוא עדיין צריך לבצע פעולות כדי להשלים את הרכישה לפני שתינתן לו הרשאה.
צריך להעניק את ההרשאה רק כשהרכישה עוברת ממצב PENDING (בהמתנה) למצב PURCHASED (נרכשה).
מידע נוסף זמין במאמר טיפול בעסקאות בהמתנה.
אם צירפתם מזהי משתמשים לרכישה, כמו שמתואר בקטע צירוף מזהי משתמשים, אתם יכולים לאחזר אותם ולהשתמש בהם כדי לשייך את הרכישה למשתמש הנכון במערכת שלכם. השיטה הזו שימושית כשמבצעים התאמה של רכישות, במקרים שבהם האפליקציה איבדה את ההקשר לגבי המשתמש שביצע את הרכישה. כדי להגן מפני ניצול לרעה, Google Play ממליצה לאמת את המזהים האלה בקצה העורפי המאובטח לפני שמעניקים הרשאות. מידע נוסף זמין במאמר אימות רכישות לפני הענקת הרשאות. חשוב לזכור: ברכישות שבוצעו מחוץ לאפליקציה, המזהים האלה לא יוגדרו. במקרה כזה, האפליקציה יכולה להעניק את ההרשאה למשתמש שמחובר לחשבון, או לבקש מהמשתמש לבחור את החשבון המועדף.
בהזמנות מראש, הרכישה נמצאת במצב PENDING לפני שמגיע מועד ההשקה. הרכישה של ההזמנה מראש תושלם בזמן ההשקה, והסטטוס ישתנה ל-PURCHASED בלי פעולות נוספות.
הודעה למשתמש
אחרי שהמשתמשים יאשרו את הרכישה, באפליקציה שלך חייבת להופיע הודעה על כך שהרכישה בוצעה בהצלחה. בזכות ההתראה, המשתמש לא מתבלבל ולא תוהה אם הרכישה הושלמה בהצלחה. בלבול כזה עלול לגרום למשתמש להפסיק להשתמש באפליקציה, לפנות לתמיכה או להתלונן על כך ברשתות החברתיות. חשוב לדעת שהאפליקציה יכולה לזהות עדכונים לגבי רכישות בכל שלב במחזור החיים שלה. לדוגמה, הורה מאשר רכישה ממתינה במכשיר אחר, ובמקרה כזה יכול להיות שהאפליקציה שלכם תרצה לעכב את ההודעה למשתמש עד לזמן המתאים. הנה כמה דוגמאות למקרים שבהם עיכוב יכול להיות מוצדק:
- הצגת הודעה במהלך קטע פעולה או קטע מעבר במשחק עלולה להסיח את דעת המשתמש. במקרה כזה, צריך להודיע למשתמש אחרי שחלק הפעולה מסתיים.
- במהלך המדריך הראשוני והגדרת המשתמש במשחק. לדוגמה, יכול להיות שמשתמש ביצע רכישה מחוץ לאפליקציה לפני שהתקין אותה. מומלץ להודיע למשתמשים חדשים על הפרס מיד אחרי שהם פותחים את המשחק או במהלך ההגדרה הראשונית של המשתמש. אם האפליקציה מחייבת את המשתמש ליצור חשבון או להתחבר לפני שהוא מקבל את ההרשאה, מומלץ להסביר למשתמש אילו שלבים צריך להשלים כדי לממש את הרכישה. זה קריטי כי אם האפליקציה לא תעבד את הרכישה, המשתמשים יקבלו החזר כספי אחרי 3 ימים.
כשמודיעים למשתמש על רכישה, Google Play ממליצה על המנגנונים הבאים:
- הצגת תיבת דו-שיח בתוך האפליקציה.
- לשלוח את ההודעה לתיבת הודעות באפליקציה, ולציין בבירור שיש הודעה חדשה בתיבת ההודעות באפליקציה.
- להשתמש בהודעת התראה של מערכת ההפעלה.
ההתראה צריכה להודיע למשתמש על ההטבה שהוא קיבל. לדוגמה, "רכשת 100 מטבעות זהב!". בנוסף, אם הרכישה בוצעה כתוצאה מהטבה של תוכנית כמו Play Pass, האפליקציה תודיע על כך למשתמש. לדוגמה, "הפריטים התקבלו! קיבלת 100 אבני חן עם Play Pass. Continue" (המשך). לכל תוכנית יכולות להיות הנחיות לגבי הטקסט המומלץ להצגה למשתמשים כדי להסביר את היתרונות.
מודיעים ל-Google שהרכישה עובדה
אחרי שהאפליקציה העניקה למשתמש את ההרשאה והודיעה לו שהעסקה בוצעה בהצלחה, היא צריכה להודיע ל-Google שהרכישה עובדה בהצלחה. כדי לעשות את זה, צריך לאשר את הרכישה תוך שלושה ימים, אחרת המשתמש יקבל החזר כספי באופן אוטומטי וההרשאה תבוטל. בקטעים הבאים מתואר התהליך לאישור סוגים שונים של רכישות.
מוצרים מתכלים
לגבי פריטים מתכלים, אם לאפליקציה יש בק-אנד מאובטח, מומלץ להשתמש ב-Purchases.products:consume כדי לצרוך רכישות באופן מהימן. כדי לוודא שהרכישה עדיין לא נוצלה, בודקים את consumptionState מהתוצאה של הקריאה ל-Purchases.products:get. אם האפליקציה היא בצד הלקוח בלבד, בלי קצה עורפי, צריך להשתמש ב-consumeAsync() מספריית החיובים ב-Google Play. שתי השיטות עומדות בדרישת האישור ומציינות שהאפליקציה העניקה למשתמש את ההרשאה.
השיטות האלה גם מאפשרות לאפליקציה להפוך את המוצר בחיוב חד-פעמי שתואם לאסימון הרכישה שהוזן לזמין לרכישה חוזרת. ב-consumeAsync() צריך גם להעביר אובייקט שמטמיע את הממשק של ConsumeResponseListener. האובייקט הזה מטפל בתוצאה של פעולת הצריכה. אפשר לשנות את השיטה onConsumeResponse(), שספריית החיובים ב-Google Play קוראת לה כשהפעולה מסתיימת.
הדוגמה הבאה ממחישה איך לצרוך מוצר באמצעות ספריית החיובים ב-Google Play, בעזרת אסימון הרכישה המשויך:
Kotlin
val consumeParams = ConsumeParams.newBuilder() .setPurchaseToken(purchase.getPurchaseToken()) .build() val consumeResult = withContext(Dispatchers.IO) { client.consumePurchase(consumeParams) }
Java
ConsumeParams consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build(); ConsumeResponseListener listener = (billingResult, purchaseToken) -> { if (billingResult.getResponseCode() == BillingResponseCode.OK) { // Handle the success of the consume operation. } }; billingClient.consumeAsync(consumeParams, listener);
מוצרים לא מתכלים
כדי לאשר רכישות של פריטים שלא ניתנים לצריכה, אם לאפליקציה יש קצה עורפי מאובטח, מומלץ להשתמש ב-Purchases.products:acknowledge כדי לאשר רכישות בצורה מהימנה. כדי לוודא שהרכישה לא אושרה בעבר, בודקים את acknowledgementState מהתוצאה של הקריאה ל-Purchases.products:get.
אם האפליקציה שלכם היא בצד הלקוח בלבד, השתמשו ב-BillingClient.acknowledgePurchase() מספריית החיובים ב-Google Play באפליקציה. לפני אישור הרכישה, האפליקציה צריכה לבדוק אם היא כבר אושרה באמצעות השיטה isAcknowledged() בספריית החיובים ב-Google Play.
הדוגמה הבאה ממחישה איך לאשר רכישה באמצעות ספריית החיובים ב-Google Play:
Kotlin
val client: BillingClient = billingClient val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchase.purchaseToken) val ackPurchaseResult = withContext(Dispatchers.IO) { client.acknowledgePurchase(acknowledgePurchaseParams.build()) }
Java
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) { if (!purchase.isAcknowledged()) { AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchase.getPurchaseToken()) .build(); billingClient.acknowledgePurchase(acknowledgePurchaseParams, (billingResult) -> { // Acknowledgment handled. }); } }
מינויים
הטיפול במינויים דומה לטיפול בפריטים שלא נצרכים. אם אפשר, השתמשו ב-Purchases.subscriptions.acknowledge מממשק ה-API של Google Play למפתחים כדי לאשר את הרכישה בצורה מהימנה מה-backend המאובטח שלכם. מוודאים שהרכישה לא אושרה בעבר על ידי בדיקת השדה acknowledgementState במשאב הרכישה מ-Purchases.subscriptions:get. אחרת, אפשר לאשר מינוי באמצעות BillingClient.acknowledgePurchase() מספריית החיובים ב-Google Play אחרי שבודקים את isAcknowledged(). צריך לאשר את כל רכישות המינויים הראשוניות. אין צורך לאשר חידושי מינויים. למידע נוסף על המקרים שבהם צריך לאשר מינויים, עיינו בנושא מכירת מינויים.
טיפול ברכישות שנדחו או שלא מולאו
אם רכישה נכשלת באימות בקצה העורפי, בבדיקות של זכאות המשתמש, בבדיקות של הונאה וניצול לרעה, או אם המערכת לא יכולה לממש את ההרשאה, אל תעניקו את ההרשאה ואל תאשרו או תצרכו את הרכישה.
רכישות שלא אושרו מבוטלות ומוחזרות באופן אוטומטי אחרי שחלון האישור פג, כרשת ביטחון לצרכן, אבל אל תסתמכו על חלון התפוגה הזה כדי לדחות רכישות. אם חלון האישור יפוג, ההזמנה תישאר במצב לא מאושר ו-Google Play לא תוכל להבחין בין דחייה מכוונת של המפתח לבין זמן קצוב לתפוגה של עיבוד בקצה העורפי.
במקום זאת, צריך לבטל מיד את הרכישות שנדחו ולתת החזר כספי דרך ה-backend באמצעות ממשק ה-API של Google Play למפתחים:
- מינויים: קוראים ל-
purchases.subscriptionsv2.revokeעם"fullRefund": {}ב-revocationContext. הפעולה הזו מבטלת את המינוי באופן מיידי, מבטלת את הגישה ויוזמת החזר כספי מלא. - מוצרים בחיוב חד-פעמי: מפעילים את
orders.refundכשהפרמטרrevokeמוגדר ל-trueכדי להנפיק החזר כספי מיידי על הזמנה ולבטל את ההרשאה שלה. כשקוראים ל-method הזו, צריך להעביר את מזהה ההזמנה של הרכישה, שאותו אפשר לאחזר באמצעותPurchase.getOrderId()אוpurchases.productsv2.getproductpurchasev2. אל תפעילו את השיטותconsumeAsync(),acknowledgePurchase(),Purchases.products:consumeאוPurchases.products:acknowledgeעל רכישות שנדחו. - התנהגות של RTDN ו-Voided Purchases: ביטולים והחזרים כספיים ביוזמת המפתח (
purchases.subscriptionsv2.revokeו-orders.refundעםrevokeשמוגדר כ-true) מפעילים התראות בזמן אמת למפתחים ומתועדים ב-Voided Purchases API (purchases.voidedpurchases.listעם שדה התגובהvoidedSourceשמוגדר כ-1למפתח):- מינויים: נשלחת התראה גם ל-
SUBSCRIPTION_REVOKEDוגם ל-VoidedPurchaseNotification. - מוצרים בחיוב חד-פעמי: נשלחת התראה מסוג
VoidedPurchaseNotification(והתראה מסוגONE_TIME_PRODUCT_CANCELEDאם האפליקציה מוגדרת ב-Play Console לקבל את כל ההתראות על מוצרים בחיוב חד-פעמי). - קריאה ל-
orders.refundבלי להגדיר אתrevokeל-trueמבצעת החזר כספי על ההזמנה בלי לבטל את ההרשאה, ולא מפעילה התראה על ביטול או על רכישה מבוטלת. למידע נוסף, קראו את המאמר ניהול מינויים ורכישות חד-פעמיות.
- מינויים: נשלחת התראה גם ל-
Recap
בהמשך מופיע סיכום של השלבים האלה.
שולחים את הרכישה לקצה העורפי המאובטח כדי לאמת רכישות לפני שמעניקים הרשאות.
לעדכן את נפח האחסון של ההרשאה בהתאם לרכישה. אם הרכישה נמצאת בסטטוס "בהמתנה", צריך לוודא שההרשאה מסומנת כ"בהמתנה" ושהמשתמש עדיין לא מקבל הטבות. מומלץ לבצע את השלב הזה בקצה העורפי המאובטח, ואפשר לשלב אותו בקריאה ל-API לקצה העורפי בשלב 1.
מודיעים למשתמש באמצעות הודעה מתאימה (אם צריך, מעכבים את ההודעה כפי שצוין קודם).
מודיעים ל-Google שהרכישה עובדה לפי השלבים שמפורטים בקטע "עיבוד רכישות".
כדי לוודא שהאפליקציה הטמיעה את השלבים האלה בצורה נכונה, אפשר לפעול לפי מדריך הבדיקה.
טיפול בעסקאות בהמתנה
ב-Google Play יש תמיכה בעסקאות בהמתנה, או בעסקאות שדורשות שלב אחד או יותר בין הזמן שבו המשתמש מתחיל את הרכישה לבין הזמן שבו אמצעי התשלום של הרכישה מעובד. האפליקציה לא אמורה להעניק הרשאה לרכישות מהסוגים האלה עד ש-Google תודיע לכם שאמצעי התשלום של המשתמש חויב בהצלחה.
לדוגמה, משתמש יכול להתחיל עסקה על ידי בחירת חנות פיזית שבה הוא ישלם מאוחר יותר במזומן. המשתמש מקבל קוד בהתראה ובאימייל. כשהמשתמש מגיע לחנות הפיזית, הוא יכול לממש את הקוד בקופה ולשלם במזומן. לאחר מכן, Google מודיעה לכם ולמשתמש שהתשלום התקבל. לאחר מכן, האפליקציה יכולה להעניק הרשאה למשתמש.
כדי להפעיל עסקאות בהמתנה באפליקציה, צריך להפעיל את enablePendingPurchases() כחלק מההפעלה של BillingClient. האפליקציה צריכה להפעיל עסקאות בהמתנה ולתמוך בהן במוצרים בחיוב חד-פעמי. לפני שמוסיפים תמיכה, חשוב להבין את מחזור החיים של הרכישה לעסקאות בהמתנה.
כשהאפליקציה מקבלת רכישה חדשה, באמצעות PurchasesUpdatedListener או כתוצאה מהפעלת queryPurchasesAsync, צריך להשתמש ב-getPurchaseState() כדי לקבוע אם מצב הרכישה הוא PURCHASED או PENDING. צריך להעניק הרשאה רק כשהמצב הוא PURCHASED.
אם האפליקציה פועלת ויש חיבור פעיל לספריית החיובים ב-Play כשהמשתמש משלים את הרכישה, מתבצעת קריאה חוזרת ל-PurchasesUpdatedListener והערך של PurchaseState הוא עכשיו PURCHASED. בשלב הזה, האפליקציה יכולה לעבד את הרכישה באמצעות השיטה הרגילה של זיהוי ועיבוד רכישות. האפליקציה צריכה גם להפעיל את queryPurchasesAsync() ב-method onResume() של האפליקציה כדי לטפל ברכישות שעברו למצב PURCHASED כשהאפליקציה לא פעלה.
כשהרכישה עוברת מ-PENDING ל-PURCHASED, הלקוח של real_time_developer_notifications מקבל התראה מסוג ONE_TIME_PRODUCT_PURCHASED או SUBSCRIPTION_PURCHASED. אם הרכישה תבוטל, תקבלו התראה מ-ONE_TIME_PRODUCT_CANCELED או מ-SUBSCRIPTION_PENDING_PURCHASE_CANCELED. זה יכול לקרות אם הלקוח לא משלים את התשלום במסגרת הזמן הנדרשת. שימו לב שתמיד אפשר להשתמש בממשק ה-API של Google Play למפתחים כדי לבדוק את המצב הנוכחי של הרכישה.
טיפול ברכישות של כמה פריטים
בגרסה 4.0 ואילך של ספריית החיובים ב-Google Play, הלקוחות יכולים לרכוש יותר מפריט אחד של אותו מוצר בחיוב חד-פעמי בעסקה אחת, על ידי ציון הכמות בעגלת הקניות. האפליקציה אמורה לטפל ברכישות בכמות גדולה ולהעניק הרשאה בהתאם לכמות הרכישות שצוינה.
כדי לאפשר רכישות של כמה פריטים, לוגיקת ההקצאה של האפליקציה צריכה לבדוק את כמות הפריטים. אפשר לגשת לשדה quantity מאחד מממשקי ה-API הבאים:
getQuantity()מספריית החיובים ב-Google Play.-
Purchases.products.quantityמממשק ה-API של Google Play למפתחים
אחרי שמוסיפים לוגיקה לטיפול ברכישות של כמה פריטים, צריך להפעיל את התכונה "כמה פריטים" למוצר המתאים בדף הניהול של מוצרים בחיוב חד-פעמי ב-Google Play Console.
שאילתה לגבי הגדרת החיוב של המשתמש
השדה getBillingConfigAsync() מספק את המדינה שבה המשתמש משתמש ב-Google Play.
אחרי שיוצרים אובייקט BillingClient, אפשר לשלוח שאילתה לגבי הגדרות החיוב של המשתמש. בקטע הקוד הבא מוסבר איך לבצע קריאה ל-getBillingConfigAsync(). מטפלים בתגובה על ידי הטמעה של BillingConfigResponseListener. המאזין הזה מקבל עדכונים על כל השאילתות להגדרת החיוב שנשלחות מהאפליקציה.
אם האובייקט BillingResult שהוחזר לא מכיל שגיאות, אפשר לבדוק את השדה countryCode באובייקט BillingConfig כדי לקבל את המדינה שמוגדרת למשתמש ב-Play.
Kotlin
// Use the default GetBillingConfigParams. val getBillingConfigParams = GetBillingConfigParams.newBuilder().build() billingClient.getBillingConfigAsync( getBillingConfigParams, object : BillingConfigResponseListener { override fun onBillingConfigResponse( billingResult: BillingResult, billingConfig: BillingConfig? ) { if (billingResult.responseCode == BillingResponseCode.OK && billingConfig != null ) { val countryCode = billingConfig.countryCode // ... } else { // Handle errors } } } )
Java
GetBillingConfigParams getBillingConfigParams = GetBillingConfigParams.newBuilder().build(); billingClient.getBillingConfigAsync( getBillingConfigParams, (billingResult, billingConfig) -> { if (billingResult.getResponseCode() == BillingResponseCode.OK && billingConfig != null) { String countryCode = billingConfig.getCountryCode(); } else { // TODO: Handle errors } });