באמצעות Billing API אפשר למכור מוצרים דיגיטליים ומינויים. ה-wrapper של C# מספק ממשק אסינכרוני עם בטיחות סוגים לספריית החיובים הבסיסית של Google Play.
מרחב שמות: PlayPcSdkManaged.Billing
סיווג הלקוח: BillingClient
יצירת הלקוח
תמיד משתמשים ב-factory כדי ליצור BillingClient. כך מוודאים שפונקציות callback בטוחות ל-Unity נרשמות באופן אוטומטי.
using UnityEngine; using System; using System.Threading.Tasks; using System.Collections.Generic; // Required SDK Namespaces using PlayPcSdkManaged.Billing; using PlayPcSdkManaged.Unity; public class BillingManager : MonoBehaviour { private BillingClient _billingClient; public void SetupBilling() { try { // Creates the client with the required UnityBillingCallbacksHandler _billingClient = PlayPcSdkFactory.CreateBillingClient(); Debug.Log("Billing Client created successfully."); } catch (Exception ex) { Debug.LogError($"Failed to create Billing Client: {ex.Message}"); } } private void OnDestroy() { // Always dispose of the client to clean up native C++ resources _billingClient?.Dispose(); } }
שאילת פרטי מוצר
לפני שאתם מציגים פריטים למכירה, אתם צריכים לשלוח שאילתה ל-Google Play כדי לקבל פרטים כמו מחיר, שם ותיאור.
public async Task GetProductDetailsAsync() { try { // Define the list of products you want to query var productList = new List{ // Use ProductType.InApp for consumables/non-consumables new ProductId { Id = "gem_pack_100", ProductType = ProductType.InApp }, // Use ProductType.Subs for subscriptions new ProductId { Id = "gold_subscription", ProductType = ProductType.InApp } }; var queryParams = new QueryProductDetailsParams { ProductIds = productList }; // Async call var result = await _billingClient.QueryProductDetailsAsync(queryParams); if (result.IsOk) { foreach (var product in result.Value.ProductDetailsList) { // The formatted price (e.g., "$0.99") is inside the ProductOffers list if (product.ProductOffers != null && product.ProductOffers.Count > 0) { var price = product.ProductOffers[0].FormattedPrice; var offerToken = product.ProductOffers[0].OfferToken; Debug.Log($"Product: {product.Title} | Price: {price} | Token: {offerToken}"); } } } else { Debug.LogError($"Query Failed: {result.Code} - {result.ErrorMessage}"); } } catch (Exception ex) { Debug.LogException(ex); } }
הפעלת תהליך הרכישה
כדי להתחיל רכישה, מתקשרים אל LaunchPurchaseFlowAsync עם OfferToken
מהשלב של פרטי המוצר.
public async Task BuyItemAsync(string offerToken) { try { var purchaseParams = new LaunchPurchaseFlowParams { OfferToken = offerToken, Quantity = 1, // Optional: Attach obfuscated IDs for fraud detection ObfuscatedAccountId = "user_12345_hash", ObfuscatedProfileId = "profile_abcde_hash" }; var result = await _billingClient.LaunchPurchaseFlowAsync(purchaseParams); if (result.IsOk) { var purchase = result.Value.ProductPurchaseDetails; Debug.Log($"Purchase Successful! Order ID: {purchase.OrderId}"); // IMPORTANT: You must now Acknowledge or Consume this purchase. // If you don't, Google Play will refund the user after a few days. if (!purchase.IsAcknowledged) { // Decide based on your game logic if it's consumable or permanent await HandlePurchaseAsync(purchase); } } else if (result.Code == BillingError.UserCanceled) { Debug.Log("User canceled the purchase flow."); } else { Debug.LogError($"Purchase Failed: {result.Code} - {result.ErrorMessage}"); } } catch (Exception ex) { Debug.LogException(ex); } }
שאילתה לגבי רכישות קיימות (שחזור)
מתקשרים אל QueryPurchasesAsync בהפעלת המשחק כדי לשחזר פריטים שכבר בבעלותכם, למשל אחרי התקנה מחדש של המשחק, או כדי לבדוק אם יש עסקאות בהמתנה.
בדוגמה הזו אפשר לראות איך להפנות רכישות אל Acknowledge עבור פריטים קבועים או אל Consume עבור פריטים מתכלים.
public async Task CheckExistingPurchasesAsync() { try { // Fetches all purchases owned by the user var result = await _billingClient.QueryPurchasesAsync(); if (result.IsOk) { foreach (var purchase in result.Value.ProductPurchaseDetails) { Debug.Log($"User owns: {purchase.ProductId} | State: {purchase.PurchaseState}"); // Process any purchase that hasn't been acknowledged yet if (purchase.PurchaseState == PurchaseState.Purchased && !purchase.IsAcknowledged) { await HandlePurchaseAsync(purchase); } } } else { Debug.LogError($"Restore Failed: {result.Code} - {result.ErrorMessage}"); } } catch (Exception ex) { Debug.LogException(ex); } } // Helper method to route purchases private async Task HandlePurchaseAsync(ProductPurchaseDetails purchase) { // Example logic: "gem_pack" is consumable, everything else is permanent if (purchase.ProductId.Contains("gem_pack")) { await ConsumeItemAsync(purchase.PurchaseToken); } else { await AcknowledgeItemAsync(purchase.PurchaseToken); } }
אישור רכישה
צריך לאשר רכישה של פריטים שאי אפשר לצרוך (לדוגמה, 'שדרוג פרימיום' או 'חבילת שלבים'). האישור הזה נשלח ל-Google Play ומשמש כמתן הרשאה מצידך להענקת הפריט למשתמש.
public async Task AcknowledgeItemAsync(string purchaseToken) { try { var acknowledgeParams = new AcknowledgePurchaseParams { PurchaseToken = purchaseToken }; var result = await _billingClient.AcknowledgePurchaseAsync(acknowledgeParams); if (result.IsOk) { Debug.Log("Purchase Acknowledged. Usage rights granted permanently."); } else { Debug.LogError($"Acknowledge Failed: {result.Code} - {result.ErrorMessage}"); } } catch (Exception ex) { Debug.LogException(ex); } }
מימוש רכישה
פריטים מתכלים, כמו '100 אבני חן' או 'שיקוי בריאות', צריכים להיות בשימוש כדי לאפשר רכישה חוזרת.
public async Task ConsumeItemAsync(string purchaseToken) { try { var consumeParams = new ConsumePurchaseParams { PurchaseToken = purchaseToken }; var result = await _billingClient.ConsumePurchaseAsync(consumeParams); if (result.IsOk) { Debug.Log("Item Consumed. User can buy it again."); // Add the gems/coins to the user's inventory here } else { Debug.LogError($"Consume Failed: {result.Code} - {result.ErrorMessage}"); } } catch (Exception ex) { Debug.LogException(ex); } }