במאמר הזה מוסבר איך לשלב את ספריית החיובים ב-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. } });
חיבור מחדש אוטומטי
עם ההשקה של השיטה enableAutoServiceReconnection() ב-BillingClient.Builder בגרסה 8.0.0, ספריית החיובים ב-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, כך שלא צריך להגדיר listener נפרד. במקום זאת, הפונקציה מושהית עד שהשאילתה מסתיימת, ואז אפשר לעבד את התוצאה:
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() מהשרשור הראשי של האפליקציה. השיטה הזו מקבלת הפניה לאובייקט 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);
השיטה 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.
ציון מחיר בהתאמה אישית
אם האפליקציה שלכם מופצת למשתמשים באיחוד האירופי, אתם צריכים להשתמש בשיטה 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 (בהמתנה) לסטטוס 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 למפתחים כדי לאשר את הרכישה בצורה מהימנה מהקצה האחורי המאובטח. מוודאים שהרכישה לא אושרה בעבר על ידי בדיקת השדה acknowledgementState במשאב הרכישה מ-Purchases.subscriptions:get. אחרת, אפשר לאשר מינוי באמצעות BillingClient.acknowledgePurchase() מספריית החיובים ב-Google Play אחרי שבודקים את isAcknowledged(). צריך לאשר את כל הרכישות הראשוניות של מינויים. אין צורך לאשר חידושי מינויים. למידע נוסף על המקרים שבהם צריך לאשר מינויים, עיינו בנושא מכירת מינויים.
טיפול ברכישות שנדחו או שלא מולאו
אם רכישה נכשלת באימות בקצה העורפי, בבדיקות התאמה, בבדיקות של הונאה וניצול לרעה, או אם המערכת לא יכולה לממש את ההרשאה, אל תעניקו את ההרשאה ואל תאשרו או תצרכו את הרכישה.
אמנם רכישות שלא אושרו מקבלות החזר כספי באופן אוטומטי והגישה אליהן נחסמת אחרי שחלון האישור מסתיים, כרשת ביטחון לצרכנים, אבל אל תסתמכו על חלון התפוגה הזה כדי לדחות רכישות. אם חלף הזמן להצגת חלון האישור, ההזמנה נשארת במצב לא מאושר, וזה לא ברור ל-Google Play. המערכת לא יכולה להבחין בין דחייה מכוונת של המפתח לבין פסק זמן של עיבוד בקצה העורפי.
במקום זאת, צריך לבטל מיד את הרכישות שנדחו ולהעביר החזר כספי מהקצה העורפי באמצעות ממשק ה-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
בהמשך מופיע סיכום של השלבים האלה.
שולחים את הרכישה לקצה העורפי המאובטח כדי לאמת רכישות לפני שמעניקים הרשאות.
מעדכנים את נפח האחסון של ההרשאה בהתאם לרכישה. אם הרכישה במצב PENDING, צריך לוודא שההרשאה מסומנת כ"בהמתנה" ושהמשתמש עדיין לא מקבל הטבות. מומלץ לבצע את השלב הזה בקצה העורפי המאובטח שלכם, ואפשר לשלב אותו בקריאה ל-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 } });