משלבים את ספריית החיובים ב-Google Play באפליקציה

כאן נסביר איך לשלב את ספריית החיוב ב-Google Play באפליקציה כדי להתחיל למכור מוצרים.

מחזור החיים של רכישה

זהו תהליך הרכישה האופייני לרכישה חד-פעמית או למינוי.

  1. להציג למשתמש מה הוא יכול לקנות.
  2. מפעילים את תהליך הרכישה כדי שהמשתמש יוכל לאשר את הרכישה.
  3. מאמתים את הרכישה בשרת.
  4. מעבירים תוכן למשתמש.
  5. מאשרים את מסירת התוכן. במוצרי צריכה, צריך לממש את הרכישה כדי שהמשתמש יוכל לקנות את הפריט שוב.

המינויים מתחדשים באופן אוטומטי עד שמבטלים אותם. למינוי יכולים להיות המצבים הבאים:

  • פעיל: המשתמש נמצא במצב תקין ויש לו גישה למינוי.
  • בוטל: המשתמש ביטל את המינוי אבל עדיין יש לו גישה עד שהתוקף שלו פג.
  • בתקופת חסד: המשתמש נתקל בבעיית תשלום אבל עדיין יש לו גישה בזמן ש-Google מנסה שוב את אמצעי התשלום.
  • בהמתנה: הייתה למשתמש בעיה בתשלום ואין לו יותר גישה בזמן ש-Google מנסה שוב את אמצעי התשלום.
  • מושהה: המשתמש השהה את הגישה שלו ולא תהיה לו גישה עד שהוא יחדש אותה.
  • התוקף פג: המשתמש ביטל את המינוי ואיבד את הגישה אליו. המשתמש נחשב כנוטש כשהתוקף שלו פג.

איך מפעילים חיבור ל-Google Play

השלב הראשון בשילוב עם מערכת החיוב של Google Play הוא הוספה של ספריית החיובים ב-Google Play לאפליקציה והפעלת החיבור.

הוספת התלות בספריית החיוב ב-Google Play

מוסיפים את התלות של ספריית החיובים ב-Google Play לקובץ build.gradle של האפליקציה, באופן הבא:

Groovy

dependencies {
    def billing_version = "7.0.0"

    implementation "com.android.billingclient:billing:$billing_version"
}

Kotlin

dependencies {
    val billing_version = "7.0.0"

    implementation("com.android.billingclient:billing:$billing_version")
}

אם אתם משתמשים ב-Kotlin, מודול ה-KTX של ספריית החיובים ב-Google Play מכיל תוספים ל-Kotlin ותמיכה ב-coroutines, שמאפשרים לכם לכתוב קוד Kotlin שתואם לשפה כשאתם משתמשים בספריית החיובים ב-Google Play. כדי לכלול את התוספים האלה בפרויקט, מוסיפים את התלות הבאה לקובץ build.gradle של האפליקציה, כפי שמוצג:

מגניב

dependencies {
    def billing_version = "7.0.0"

    implementation "com.android.billingclient:billing-ktx:$billing_version"
}

Kotlin

dependencies {
    val billing_version = "7.0.0"

    implementation("com.android.billingclient:billing-ktx:$billing_version")
}

איך מפעילים את BillingClient

אחרי שמוסיפים יחסי תלות לספריית החיובים ב-Google Play, צריך לאתחל מופע של BillingClient. BillingClient הוא הממשק הראשי לתקשורת בין ספריית החיוב ב-Google Play לבין שאר האפליקציה. BillingClient מספק שיטות נוחות, סינכרוניות ואסינכרוניות, להרבה פעולות חיוב נפוצות. מומלץ מאוד לפתוח חיבור פעיל אחד של BillingClient בכל פעם, כדי להימנע מכמה קריאות חזרה (callbacks) של PurchasesUpdatedListener לאירוע אחד.

כדי ליצור BillingClient, משתמשים ב-newBuilder(). אפשר להעביר כל הקשר ל-newBuilder(), ו-BillingClient משתמש בו כדי לקבל הקשר של אפליקציה. המשמעות היא שאין לכם מה לדאוג לגבי דליפות זיכרון. כדי לקבל עדכונים על רכישות, צריך גם לבצע קריאה ל-setListener() ולהעביר הפניה ל-PurchasesUpdatedListener. ה-listen הזה מקבל עדכונים לגבי כל הרכישות באפליקציה שלכם.

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 כדי לקבל קריאה חוזרת אחרי שהלקוח מוכן לשלוח בקשות נוספות.

בנוסף, צריך להטמיע לוגיקה של ניסיונות חוזרים כדי לטפל בחיבורים שאבדו ל-Google Play. כדי להטמיע לוגיקה של ניסיונות חוזרים, משנים את השיטה onBillingServiceDisconnected() של הקריאה החוזרת (callback), ומוודאים שה-BillingClient קורא לשיטה startConnection() כדי להתחבר מחדש ל-Google Play לפני שליחת בקשות נוספות.

הדוגמה הבאה מראה איך מתחילים חיבור ובודקים שהוא מוכן לשימוש:

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() {
        // 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.
        }
    }
    @Override
    public void onBillingServiceDisconnected() {
        // Try to restart the connection on the next request to
        // Google Play by calling the startConnection() method.
    }
});

הצגת מוצרים שזמינים לרכישה

אחרי שתיצרו חיבור ל-Google Play, תוכלו להריץ שאילתות לגבי המוצרים הזמינים ולהציג אותם למשתמשים.

שליחת שאילתות לגבי פרטי המוצרים היא שלב חשוב לפני הצגת המוצרים למשתמשים, כי היא מאפשרת לקבל מידע על המוצרים שמותאם לאזור שלהם. לגבי מינויים, חשוב לוודא שהצגת המוצר עומדת בכל כללי המדיניות של Play.

כדי לשלוח שאילתה לגבי פרטי מוצרים מתוך האפליקציה, קוראים לפונקציה queryProductDetailsAsync().

כדי לטפל בתוצאה של הפעולה האסינכרונית, צריך גם לציין מכשיר שמיעה שמטמיע את הממשק ProductDetailsResponseListener. לאחר מכן תוכלו לשנות את onProductDetailsResponse(), שמעדכן את המאזין כשהשאילתה מסתיימת, כפי שמתואר בדוגמה הבאה:

Kotlin

val queryProductDetailsParams =
    QueryProductDetailsParams.newBuilder()
        .setProductList(
            ImmutableList.of(
                Product.newBuilder()
                    .setProductId("product_id_example")
                    .setProductType(ProductType.SUBS)
                    .build()))
        .build()

billingClient.queryProductDetailsAsync(queryProductDetailsParams) {
    billingResult,
    productDetailsList ->
      // check billingResult
      // process returned productDetailsList
}

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,
                List<ProductDetails> productDetailsList) {
            // check billingResult
            // process returned productDetailsList
        }
    }
)

כששולחים שאילתה לגבי פרטי המוצר, מעבירים מופע של 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 Billing Library 5.

עיבוד התוצאה

ספריית החיובים ב-Google Play שומרת את תוצאות השאילתה ב-List של אובייקטים מסוג ProductDetails. לאחר מכן תוכלו להפעיל מגוון שיטות על כל אובייקט ProductDetails ברשימה כדי להציג מידע רלוונטי על מוצר בתוך האפליקציה, כמו המחיר או התיאור שלו. כדי לצפות במידע הזמין על פרטי המוצר, ראו את רשימת השיטות במחלקה ProductDetails.

לפני שמציעים פריט למכירה, צריך לבדוק שהפריט כבר לא בבעלות המשתמש. אם למשתמש יש פריט חד-פעמי שעדיין נמצא בספריית הפריטים שלו, הוא צריך להשתמש בפריט לפני שהוא יוכל לקנות אותו שוב.

לפני שמציעים מינוי, חשוב לוודא שהמשתמש עדיין לא רשום כמנוי. כמו כן, חשוב לשים לב לדברים הבאים:

  • הפונקציה queryProductDetailsAsync() מחזירה את פרטי המוצרים במינוי ומקסימום 50 מבצעים לכל מינוי.
  • 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)
        // For One-time product, "setOfferToken" method shouldn't be called.
        // For subscriptions, to get an offer token, call ProductDetails.subscriptionOfferDetails()
        // 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<ProductDetailsParams> productDetailsParamsList =
    ImmutableList.of(
        ProductDetailsParams.newBuilder()
             // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
            .setProductDetails(productDetails)
            // For one-time products, "setOfferToken" method shouldn't be called.
            // For subscriptions, to get an offer token, call
            // ProductDetails.subscriptionOfferDetails() 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 מוצג מינוי שזמין לרכישה
איור 1. במסך הרכישה ב-Google Play מוצג מינוי שזמין לרכישה.

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) {
           handlePurchase(purchase)
       }
   } else if (billingResult.responseCode == BillingResponseCode.USER_CANCELED) {
       // Handle an error caused by a user cancelling 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) {
            handlePurchase(purchase);
        }
    } else if (billingResult.getResponseCode() == BillingResponseCode.USER_CANCELED) {
        // Handle an error caused by a user cancelling the purchase flow.
    } else {
        // Handle any other error codes.
    }
}

אחרי רכישה מוצלחת, תוצג מסך של רכישה מוצלחת ב-Google Play, בדומה לזה שמוצג באיור 2.

מסך הרכישה המוצלח ב-Google Play
איור 2. מסך הצלחת הרכישה ב-Google Play.

רכישה מוצלחת יוצרת גם אסימון רכישה, שהוא מזהה ייחודי שמייצג את המשתמש ואת מזהה המוצר של המוצר שרכש מתוך האפליקציה. האפליקציות שלכם יכולות לאחסן את אסימון הרכישה באופן מקומי, אבל אנחנו ממליצים להעביר את האסימון לשרת העורפי המאובטח. שם תוכלו לאמת את הרכישה ולהגן מפני הונאה. התהליך הזה מפורט בהמשך.

המשתמש יקבל גם קבלה באימייל על העסקה עם מזהה ההזמנה או המזהה הייחודי של העסקה. המשתמשים מקבלים אימייל עם מזהה הזמנה ייחודי לכל רכישה חד-פעמית של מוצר, וגם עבור רכישת המינוי הראשונית והחידושים האוטומטיים הקבועים הבאים. אפשר להשתמש במספר ההזמנה כדי לנהל את ההחזרים הכספיים ב-Google Play Console.

ציון מחיר בהתאמה אישית

אם האפליקציה שלכם ניתנת להפצה למשתמשים באיחוד האירופי, עליכם להשתמש בשיטה setIsOfferPersonalized() כדי להודיע למשתמשים שהמחיר של פריט מותאם אישית באמצעות קבלת החלטות אוטומטית.

מסך הרכישה ב-Google Play, שמציין שהמחיר הותאם אישית עבור המשתמש.
איור 3. מסך הרכישה ב-Google Play שמציין שהמחיר הותאם אישית עבור המשתמש.

צריך להתייעץ עם Art. 6 (1) (ea) CRD של ה- Consumer Rights Directive 2011/83/EU כדי לקבוע אם המחיר שאתם מציעים למשתמשים הוא מותאם אישית.

setIsOfferPersonalized() מקבל קלט בוליאני. כשהערך של true הוא true, הודעה על הגילוי הנאות מופיעה בממשק המשתמש של Play. כשהערך של false הוא 0, הודעה על הגילוי הנאות לא מוצגת בממשק המשתמש. ערך ברירת המחדל הוא false.

מידע נוסף זמין במרכז העזרה לצרכנים.

מתבצע עיבוד של הרכישות

אחרי שמשתמש משלים רכישה, האפליקציה צריכה לעבד את הרכישה הזו. ברוב המקרים, האפליקציה מקבלת התראות על רכישות דרך PurchasesUpdatedListener. עם זאת, יש מקרים שבהם האפליקציה תקבל הודעה על רכישות באמצעות קריאה ל-BillingClient.queryPurchasesAsync(), כפי שמתואר בקטע אחזור רכישות.

בנוסף, אם יש לכם לקוח התראות בזמן אמת למפתחים בקצה העורפי המאובטח, תוכלו לרשום רכישות חדשות על ידי קבלת subscriptionNotification או oneTimeProductNotification שמציינים רכישה חדשה. אחרי קבלת ההתראות האלה, צריך להפעיל את Google Play Developer API כדי לקבל את הסטטוס המלא ולעדכן את המצב של הקצה העורפי.

האפליקציה צריכה לעבד רכישה באופן הבא:

  1. מאמתים את הרכישה.
  2. מעבירים את התוכן למשתמש ומאשרים את מסירת התוכן. לחלופין, אפשר לסמן את הפריט כפריט שנצרך כדי שהמשתמש יוכל לקנות שוב את הפריט.

כדי לאמת רכישה, קודם צריך לבדוק שסטטוס הרכישה הוא PURCHASED. אם סכום הרכישה הוא PENDING, צריך לעבד את הרכישה כפי שמתואר במאמר טיפול בעסקאות בהמתנה. ברכישות שמתקבלות מ-onPurchasesUpdated() או מ-queryPurchasesAsync(), עליכם לאמת את הרכישה כדי לוודא שהיא לגיטימית לפני שהאפליקציה מעניקה את ההרשאה. במאמר אימות רכישות לפני הענקת הרשאות מוסבר איך מאמתים רכישות בצורה נכונה.

אחרי שתאמתו את הרכישה, האפליקציה תהיה מוכנה להעניק את ההרשאה למשתמש. אפשר לזהות את חשבון המשתמש המשויך לרכישה באמצעות הערך של ProductPurchase.obfuscatedExternalAccountId שמוחזר על ידי Purchases.products:get לרכישות של מוצרים באפליקציה, הערך של SubscriptionPurchase.obfuscatedExternalAccountId שמוחזר על ידי Purchases.subscriptions:get למינויים בצד השרת, או הערך של obfuscatedAccountId מ-Purchase.getAccountIdentifiers() בצד הלקוח, אם הוגדר ערך באמצעות setObfuscatedAccountId בזמן הרכישה.

אחרי שהמשתמשים יאשרו את הרכישה, חייב להופיע באפליקציה שלך אישור על הרכישה. האישור הזה מדווח ל-Google Play כמתן הרשאה מצידך לרכישה.

התהליך להענקת הזכאות ולאישור הרכישה משתנה בהתאם לסוג הרכישה: פריטים לשימוש חד-פעמי, פריטים לשימוש חוזר או מינויים.

מוצרים מתכלים

לגבי פריטים חד-פעמיים, אם לאפליקציה יש קצה עורפי מאובטח, מומלץ להשתמש ב-Purchases.products:consume כדי לנצל את הרכישות בצורה מהימנה. כדי לוודא שהרכישה לא נוצלה, בודקים את הערך של consumptionState בתוצאה של הקריאה ל-Purchases.products:get. אם האפליקציה היא לקוח בלבד ללא קצה עורפי, צריך להשתמש ב-consumeAsync() מתוך ספריית החיובים ב-Google Play. שתי השיטות עומדות בדרישת האישור ומציינות שהאפליקציה העניקה למשתמש הרשאה. השיטות האלה מאפשרות גם לאפליקציה להציע מחדש את המוצר החד-פעמי שתואם לאסימון הרכישה שהוזן. עם consumeAsync(), צריך גם להעביר אובייקט שמטמיע את ממשק ConsumeResponseListener. האובייקט הזה מטפל בתוצאה של פעולת הצריכה. אפשר לבטל את השיטה onConsumeResponse(), שספריית החיובים ב-Google Play קוראת לה בסיום הפעולה.

הדוגמה הבאה ממחישה שימוש במוצר באמצעות ספריית החיוב ב-Google Play באמצעות אסימון הרכישה המשויך:

Kotlin

suspend fun handlePurchase(purchase: Purchase) {
    // Purchase retrieved from BillingClient#queryPurchasesAsync or your PurchasesUpdatedListener.
    val purchase : Purchase = ...;

    // Verify the purchase.
    // Ensure entitlement was not already granted for this purchaseToken.
    // Grant entitlement to the user.

    val consumeParams =
        ConsumeParams.newBuilder()
            .setPurchaseToken(purchase.getPurchaseToken())
            .build()
    val consumeResult = withContext(Dispatchers.IO) {
        client.consumePurchase(consumeParams)
    }
}

Java

void handlePurchase(Purchase purchase) {
    // Purchase retrieved from BillingClient#queryPurchasesAsync or your PurchasesUpdatedListener.
    Purchase purchase = ...;

    // Verify the purchase.
    // Ensure entitlement was not already granted for this purchaseToken.
    // Grant entitlement to the user.

    ConsumeParams consumeParams =
        ConsumeParams.newBuilder()
            .setPurchaseToken(purchase.getPurchaseToken())
            .build();

    ConsumeResponseListener listener = new ConsumeResponseListener() {
        @Override
        public void onConsumeResponse(BillingResult billingResult, String 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 = ...
val acknowledgePurchaseResponseListener: AcknowledgePurchaseResponseListener = ...

suspend fun handlePurchase() {
    if (purchase.purchaseState === PurchaseState.PURCHASED) {
        if (!purchase.isAcknowledged) {
            val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                    .setPurchaseToken(purchase.purchaseToken)
            val ackPurchaseResult = withContext(Dispatchers.IO) {
               client.acknowledgePurchase(acknowledgePurchaseParams.build())
            }
        }
     }
}

Java

BillingClient client = ...
AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = ...

void handlePurchase(Purchase purchase) {
    if (purchase.getPurchaseState() == PurchaseState.PURCHASED) {
        if (!purchase.isAcknowledged()) {
            AcknowledgePurchaseParams acknowledgePurchaseParams =
                AcknowledgePurchaseParams.newBuilder()
                    .setPurchaseToken(purchase.getPurchaseToken())
                    .build();
            client.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
        }
    }
}

מינויים

הטיפול במינויים דומה לטיפול בפריטים שלא מתכלים. אם אפשר, כדאי להשתמש ב-Purchases.subscriptions.acknowledge מ-Google Play Developer API כדי לאשר את הרכישה בצורה מהימנה מהקצה העורפי המאובטח. בודקים את הערך של acknowledgementState במשאב הרכישה ב-Purchases.subscriptions:get כדי לוודא שהרכישה לא אושרה בעבר. לחלופין, אפשר לאשר מינוי באמצעות BillingClient.acknowledgePurchase() בספריית החיוב ב-Google Play אחרי בדיקה של isAcknowledged(). צריך לאשר את כל הרכישות הראשוניות של מינויים. אין צורך לאשר את חידוש המינוי. למידע נוסף על המקרים שבהם צריך לאשר מינויים, עיינו בנושא מכירת מינויים.

מתבצע אחזור של רכישות

האזנה לעדכוני רכישות באמצעות PurchasesUpdatedListener לא מספיקה כדי לוודא שהאפליקציה תעבד את כל הרכישות. יכול להיות שהאפליקציה לא תהיה מודעת לכל הרכישות שהמשתמש ביצע. הנה כמה תרחישים שבהם האפליקציה עלולה לא לעקוב אחרי רכישות או לא לדעת שהן בוצעו:

  • בעיות ברשת במהלך הרכישה: משתמש מבצע רכישה מוצלחת ומקבל אישור מ-Google, אבל החיבור של המכשיר לרשת התנתק לפני שהמכשיר קיבל הודעה על הרכישה דרך PurchasesUpdatedListener.
  • מספר מכשירים: משתמש קונה פריט במכשיר אחד ומצפה לראות את הפריט כשהוא מחליף מכשיר.
  • טיפול ברכישות שמתבצעות מחוץ לאפליקציה: רכישות מסוימות, כמו מימוש מבצעים, אפשר לבצע מחוץ לאפליקציה.

כדי לטפל במצבים האלה, חשוב לוודא שהאפליקציה קוראת ל-BillingClient.queryPurchasesAsync() בשיטה onResume() כדי לוודא שכל הרכישות מעובדות בהצלחה, כפי שמתואר בקטע עיבוד רכישות.

בדוגמה הבאה מוסבר איך לאחזר את רכישות המינויים של משתמש. שימו לב ש-queryPurchasesAsync() מחזיר רק מינויים פעילים ורכישות חד-פעמיות שלא נצרכו.

Kotlin

val params = QueryPurchasesParams.newBuilder()
               .setProductType(ProductType.SUBS)

// uses queryPurchasesAsync Kotlin extension function
val purchasesResult = billingClient.queryPurchasesAsync(params.build())

// check purchasesResult.billingResult
// process returned purchasesResult.purchasesList, e.g. display the plans user owns

Java

billingClient.queryPurchasesAsync(
    QueryPurchasesParams.newBuilder()
      .setProductType(ProductType.SUBS)
      .build(),
    new PurchasesResponseListener() {
      public void onQueryPurchasesResponse(BillingResult billingResult, List<Purchase> purchases) {
        // check billingResult
        // process returned purchase list, e.g. display the plans user owns

      }
    }
);

טיפול ברכישות שבוצעו מחוץ לאפליקציה

רכישות מסוימות יכולות להתרחש מחוץ לאפליקציה, למשל מימוש מבצעים או תזכורות לנטישה של עגלת הקניות לגבי רכישות מתוך האפליקציה ב-Google Play Games (IAP). כשמשתמש מבצע רכישה מחוץ לאפליקציה, הוא מצפה שהאפליקציה תציג הודעה באפליקציה או תשתמש במנגנון התראות כלשהו כדי להודיע לו שהאפליקציה קיבלה ועיבדה את הרכישה בצורה תקינה. המנגנונים המקובלים הם:

  • הצגת חלון קופץ באפליקציה.
  • להעביר את ההודעה לתיבת הודעות באפליקציה, ולציין בבירור שיש הודעה חדשה בתיבת ההודעות באפליקציה.
  • להשתמש בהודעה של מערכת ההפעלה.

חשוב לזכור שהאפליקציה יכולה להיות בכל מצב כשהיא מזהה את הרכישה. יכול להיות שהאפליקציה לא תהיה מותקנת בזמן הרכישה. המשתמשים מצפים לקבל את הרכישה שלהם כשהם ממשיכים להשתמש באפליקציה, ללא קשר למצב שבו היא נמצאת.

צריך לזהות רכישות, בלי קשר למצב שבו האפליקציה נמצאת בזמן ביצוע הרכישה. עם זאת, יש כמה מקרים חריגים שבהם אפשר לבקש לא להודיע למשתמש באופן מיידי שהפריט התקבל. לדוגמה:

  • במהלך חלק הפעולה במשחק, שבו הצגת הודעה עשויה להסיח את דעת המשתמש. במקרה כזה, עליכם להודיע למשתמש אחרי שחלק הפעולה מסתיים.
  • במהלך סצנות מעבר, שבהן הצגת הודעה עלולה להסיח את דעת המשתמש. במקרה כזה, עליכם להודיע למשתמש אחרי סיום סצנת ה-cutscene.
  • במהלך המדריך הראשוני ובחלקים של הגדרת המשתמש במשחק. מומלץ להודיע למשתמשים חדשים על התגמול מיד אחרי שהם פותחים את המשחק או במהלך ההגדרה הראשונית של המשתמש. עם זאת, מותר להמתין עד שהסצנה הראשית של המשחק תהיה זמינה כדי להודיע למשתמש.

תמיד חשוב לחשוב על המשתמש כשמחליטים מתי ואיך להודיע למשתמשים על רכישות שבוצעו מחוץ לאפליקציה. אם משתמש לא יקבל התראה באופן מיידי, הוא עלול להתבלבל, להפסיק להשתמש באפליקציה, לפנות לתמיכה למשתמשים או להתלונן עליה ברשתות החברתיות.

תזכורות על עזיבה של עגלת קניות בדף הבית של Google Play Games (מופעלות כברירת מחדל)

למפתחי משחקים שמייצרים הכנסות באמצעות IAP, אחת הדרכים שבהן ניתן למכור יחידות לשמירת מניות (מק"טים) שפעילות ב-Google Play המסוף היא באמצעות התכונה 'תזכורת לנטישה של עגלת הקניות', שמעודדת את המשתמשים להשלים את הרכישות שהם נטשו בזמן שהם גולשים בחנות Google Play. הרכישות האלה מתבצעות מחוץ לאפליקציה, מדף הבית של Google Play Games בחנות Google Play.

התכונה הזו מופעלת כברירת מחדל כדי לעזור למשתמשים להמשיך מהמקום שבו הם הפסיקו, וכדי לעזור למפתחים למקסם את המכירות. עם זאת, אפשר לבטל את ההסכמה לשימוש בתכונה הזו באפליקציה על ידי שליחת טופס ביטול ההסכמה לשימוש בתכונה 'תזכורת על עזיבה של עגלת קניות'. במאמר יצירת מוצר מתוך האפליקציה מפורטות שיטות מומלצות לניהול מק"טים ב-Google Play Console.

בתמונות הבאות מוצגת התזכורת על עזיבה של עגלת קניות שמופיעה בחנות Google Play:

במסך של חנות Google Play מוצגת
    בקשת רכישה לגבי רכישה שננטשת בעבר
איור 2. במסך של חנות Google Play מוצגת בקשה להשלים רכישה שננטשה בעבר.

במסך של חנות Google Play מוצגת בקשה להשלים רכישה שננטשה בעבר
איור 3. במסך של חנות Google Play מוצגת בקשה להשלים רכישה שננטשה בעבר.

טיפול בעסקאות בהמתנה

Google Play תומך בעסקאות בהמתנה, כלומר עסקאות שדורשות שלב נוסף אחד או יותר בין המועד שבו המשתמש מתחיל את הרכישה לבין המועד שבו אמצעי התשלום לרכישה עובר עיבוד. האפליקציה לא אמורה להעניק הרשאה לרכישות מהסוגים האלה עד ש-Google תודיע לכם שאמצעי התשלום של המשתמש חויב בהצלחה.

לדוגמה, משתמש יכול ליזום עסקה על ידי בחירת חנות פיזית, שבה הוא ישלם אחר כך במזומן. המשתמש מקבל קוד גם בהתראה וגם באימייל. כשהמשתמש מגיע לחנות הפיזית, הוא יכול לממש את הקוד בקופה ולשלם במזומן. לאחר מכן, Google מודיעה לכם וגם למשתמש שהתשלום התקבל. לאחר מכן האפליקציה תוכל להעניק למשתמש את ההרשאה.

קוראים ל-enablePendingPurchases() כחלק מהפעלת BillingClient כדי להפעיל עסקאות בהמתנה באפליקציה. האפליקציה צריכה לאפשר ולתמוך בעסקאות בהמתנה למוצרים חד-פעמיים. לפני שמוסיפים תמיכה, חשוב להבין את מחזור החיים של הרכישה לגבי עסקאות בהמתנה.

כשמתבצעת רכישה חדשה באפליקציה, דרך PurchasesUpdatedListener או כתוצאה מהפעלת queryPurchasesAsync(), משתמשים בשיטה getPurchaseState() כדי לקבוע אם מצב הרכישה הוא PURCHASED או PENDING. צריך להקצות את ההרשאה רק כשהסטטוס הוא PURCHASED.

אם האפליקציה פועלת כשהמשתמש משלים את הרכישה, PurchasesUpdatedListener ייכלל שוב בקריאה, והערך של PurchaseState יהיה PURCHASED. בשלב הזה, האפליקציה יכולה לעבד את הרכישה באמצעות השיטה הרגילה לעיבוד רכישות. האפליקציה צריכה גם להפעיל את queryPurchasesAsync() בשיטה onResume() של האפליקציה כדי לטפל ברכישות שעברו למצב PURCHASED בזמן שהאפליקציה לא הייתה פועלת.

כשהרכישה עוברת מ-PENDING ל-PURCHASED, הלקוח של התראות בזמן אמת למפתחים מקבל התראה מסוג ONE_TIME_PRODUCT_PURCHASED או SUBSCRIPTION_PURCHASED. אם הרכישה מבוטלת, תקבלו התראה מסוג ONE_TIME_PRODUCT_CANCELED או SUBSCRIPTION_PENDING_PURCHASE_CANCELED. זה יכול לקרות אם הלקוח לא משלים את התשלום במסגרת הזמן הנדרשת. חשוב לזכור שאפשר תמיד להשתמש ב-Google Play Developer API כדי לבדוק את המצב הנוכחי של רכישה.

טיפול ברכישות בכמות גדולה

ב-Google Play יש תמיכה באפשרות הזו בגרסאות 4.0 ואילך של ספריית החיוב ב-Google Play. הלקוחות יכולים לרכוש יותר מפריט אחד מאותו מוצר מתוך האפליקציה בעסקה אחת, על ידי ציון הכמות בעגלת הקניות. האפליקציה שלכם אמורה לטפל ברכישות בכמות גדולה ולהעניק את הזכאות על סמך כמות הרכישה שצוינה.

כדי לטפל ברכישות בכמות גדולה, לוגיקת הקצאת המשאבים של האפליקציה צריכה לבדוק את כמות הפריט. אפשר לגשת לשדה quantity דרך אחד מממשקי ה-API הבאים:

לאחר הוספת הלוגיקה לטיפול ברכישות בכמות גדולה, צריך להפעיל את התכונה 'כמות גדולה' למוצר המתאים בדף ניהול המוצרים מתוך האפליקציה ב-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 {
                // TODO: Handle errors
            }
        }
    })

Java

// Use the default GetBillingConfigParams.
GetBillingConfigParams getBillingConfigParams = GetBillingConfigParams.newBuilder().build();
billingClient.getBillingConfigAsync(getBillingConfigParams,
    new BillingConfigResponseListener() {
      public void onBillingConfigResponse(
          BillingResult billingResult, BillingConfig billingConfig) {
        if (billingResult.getResponseCode() == BillingResponseCode.OK
            && billingConfig != null) {
            String countryCode = billingConfig.getCountryCode();
            ...
         } else {
            // TODO: Handle errors
        }
      }
    });