کار با پرداخت درون برنامه‌ای - Unity

در این مستند ما نحوه‌ی کار با پرداخت درون برنامه‌ای در دو حالت امن و غیر امن را خواهیم دید.

پیش‌نیازها

  1. در صورتی که با سرویس درون پرداخت امن آشنایی ندارید، به معرفی سرویس درون پرداخت امن مراجعه کنید.
  2. در صورتی که هنوز در پنل توسعه‌دهنده خود سرویس پرداخت درون برنامه‌ای را راه‌اندازی نکرده‌اید، به تنظیمات پنل مراجعه کنید.
  3. در صورتی که هنوز SDK یونیتی را راه‌اندازی نکرده‌اید، به راه‌اندازی SDK بکتوری در یونیتی مراجعه کنید.

راه‌اندازی سرویس پرداخت درون برنامه‌ای

برای استفاده از این سرویس باید اسکریپت BacktoryIapBehaviour را به GameObject خالی که در راه‌اندازی SDK یونیتی ایجاد کرده‌اید، مطابق مسیر زیر اضافه کنید.

EmptyGameObject -> Add Component -> Scripts -> Backtory.InAppPurchase.Public ->
Backtory Iap Behavior

ایجاد شیء پرداخت درون برنامه‌ای

اولین قدم برای کار با پرداخت درون برنامه‌ای، ایجاد شیء BacktoryIap و انجام برخی تنظیمات بر روی آن است. تابع سازنده (Constructor) این کلاس سه آرگومان می‌گیرد: یک رشته حاوی کلید RSA که در پنل توسعه‌دهندگان کافه بازار در صفحه‌ی اپلیکیشن شما، در قسمت «پرداخت درون برنامه‌ای» به شما داده می‌شود؛ یک Listener برای آگاهی از موفقیت یا شکست پرداخت‌ها؛ و یک رشته معادل با package name برنامه. این package name باید دقیقا برابر با نام پکیجی باشد که در قسمت ‌Build Settings، در قسمت Player Settings، در برگه‌ی Other Settings در فیلد ورودی Package Name می‌نویسید؛ و هر دوی این نام‌ها باید با package name بسته‌ای که روی کافه‌بازار آپلود می‌کنید، یکی باشند. کد زیر این کار را برای شما انجام می‌دهد:

using System.Collections.Generic;
using Backtory.InAppPurchase.Public;
using UnityEngine;

public class MyBehaviour : MonoBehaviour, IBacktoryIapListener
{
  private BacktoryIap backtoryIap;

  public void Init()
  {
    backtoryIap = new BacktoryIap("<CafeBazaar-RSA-Key>", this, "com.mycompany.mygame");
  }

  public void OnGetSkuDetailsFinished(IapResult result, IList<SkuDetails> skuDetailsList)
  {
    throw new System.NotImplementedException();
  }

  public void OnGetPurchasesFinished(IapResult result, IList<string> ownedSkus, string continuationToken)
  {
    throw new System.NotImplementedException();
  }

  public void OnPurchaseFinished(IapResult result, Purchase purchase, string webhookMessage)
  {
    throw new System.NotImplementedException();
  }

  public void OnConsumptionFinished(IapResult result, string sku, string purchaseToken, string webhookMessage)
  {
    throw new System.NotImplementedException();
  }

  public void OnSubscriptionFinished(IapResult result, Purchase purchase, string webhookMessage)
  {
    throw new System.NotImplementedException();
  }
}

در کد بالا نکاتی که می‌بایست مد نظر شما قرار بگیرند، از این قرار هستند:

  1. ابتدا کلاس MyBehaviour که از کلاس MonoBehaviour اندروید ارث‌بری کرده و واسط IBacktoryIapListener را پیاده‌سازی می‌کند، ایجاد شده است. در نتیجه توانسته‌ایم این کلاس را به عنوان آرگومان ورودی Constructor شیء پرداخت درون برنامه‌ای پاس دهیم.
  2. یک نمونه از شیء پرداخت درون برنامه‌ای در تابع Init ایجاد شده است. شما باید تابع Init را در یکی از مقاطع ابتدایی ایجاد MonoBehaviour، مثلا در Start صدا بزنید.
  3. توابع مربوط به لیسنر IBacktoryIapListener همگی پیاده‌سازی شده اند که گام‌به‌گام به توضیح آن‌ها خواهیم پرداخت.

دریافت محصولات قابل خرید

یکی از اولین امکاناتی که شیء backtoryIap برای شما فراهم می‌کند، این است که می‌توانید جزئیات محصولات قابل خرید را از بازار بپرسید. برای این هدف کدی مانند زیر را می‌توانید بنویسید:

var skuList = new List<string> {
    "gas",          // 1st sku to get details about
    "premium"};     // 2nd sku
backtoryIap.GetSkuDetailsInBackground(skuList);

نتیجه‌ی اجرای کد بالا در لیسنر زیر به دست شما خواهد رسید:

public void OnGetSkuDetailsFinished(IapResult result, IList<SkuDetails> skuDetailsList)
{
    Debug.Log("getSkuDetails finished.");
    Debug.Log("request code: " + result.RequestCode);   // It is always -1,
                                            // which means NO request code!
    Debug.Log("result code: " + result.ResultCode
        + " and message: " + result.Message);       // All result codes are
                                          // found in IapResult source code.
    if (result.ResultCode == IapResult.SUCCESS) {
        foreach(SkuDetails sd in skuDetailsList) {
            Debug.Log("productId: " + sd.ProductId + ", price: " + sd.Price);
        }
    } else {
        // Failed for some reason.
        // `skuDetailsList` would be null.
    }
}

دریافت محصولات خریداری شده

می‌توانید با فراخوانی تابع getPurchases بفهمید که کاربر چه خریدهایی را از برنامه‌ی شما داشته است. همان‌طور که پیش‌تر گفته شد، محصولات کافه‌بازار به دو دسته‌ی خریدنی و اشتراکی دسته بندی می‌شوند. برای دریافت محصولات هر یک از دو دسته که کاربر آن‌ها را خریداری کرده است، می‌توانید از یکی از دو خط کد زیر استفاده کنید:

backtoryIap.GetPurchases(BacktoryIap.ITEM_TYPE_INAPP, null);     // Purchased products
// Or
backtoryIap.GetPurchases(BacktoryIap.ITEM_TYPE_SUBSCRIPTION, null); // Subscribed products

نتیجه‌ی اجرای کد بالا در لیسنر زیر به دست شما خواهد رسید:

public void OnGetPurchasesFinished(IapResult result, IList<string> ownedSkus, string continuationToken)
{
    Debug.Log("getPurchases finished.");
    Debug.Log("request code: " + result.RequestCode);   // It is always -1,
                                            // which means NO request code!
    Debug.Log("result code: " + result.ResultCode
                 + " and message: " + result.Message);  // All result codes are
                                              // found in IapResult source code.
    if (result.ResultCode == IapResult.SUCCESS) {
        Debug.Log("Owned product-ids are:");
        foreach(string os in ownedSkus) {
            Debug.Log(os);
        }

        if (continuationToken != null) {
            // call getPurchases again,
            // and pass in the token to retrieve more items
        }
    } else {
        // Failed for some reason.
        // `ownedSkus` and `continuationToken` would be null.
    }
}

خرید محصول

برای خرید یک محصول درون برنامه‌ای، می‌توانید از طریق متد زیر اقدام نمایید. آرگومان اول این تابع، شناسه‌ی کالاست که تحت عنوان (sku (stock keeping unit نیز شناخته می‌شود. آرگومان دوم نیز Developer Payload نام دارد. شما می‌توانید یک رشته‌ی دلخواه را به جای آن پاس دهید تا بازار آن را به همراه پاسخ خرید برگرداند. بعدا نیز به هنگام جستار در مورد این خرید از طریق API توسعه‌دهندگان کافه بازار می‌توانید به این رشته دسترسی یابید.

backtoryIap.InsecurePurchase("gas", "<Developer-Payload>");

نکته: بهتر است به جای Developer Payload رشته‌ای بفرستید که به برنامهٔ شما کمک کند تا کاربری که خرید را انجام داده را شناسایی کنید. اما در صورتی که مایل به این کار نیستید، می‌توانید یک رشته‌ی خالی نیز به جای آن پاس دهید. در مستندات پیاده‌سازی کافه بازار می‌توانید توضیحات بیشتری در این زمینه مطالعه کنید.

همان‌طور که از اسم تابع پیداست، این تابع خرید ناامن است؛ به این معنا که، توسط اپلیکیشن مخرب Lucky Patcher روی دستگاه اندرویدی کاربر به راحتی هک می‌شود. برای این‌که از صحت انجام خرید مطمئن شوید، می‌توانید از حالت امن تابع خرید به صورت زیر استفاده کنید:

backtoryIap.SecurePurchase("gas", "<Developer-Payload>", "<webhook-metaData>");

تابع خرید امن عینا همان کاری را برای خرید انجام می‌دهد که تابع خرید ناامن؛ با این تفاوت که پس از انجام گرفتن خرید، از سرور بکتوری (و به تبع آن سرور کافه‌بازار) صحت انجام خرید را جویا می‌شود. آرگومان‌های این دو تابع نیز مشابه اند، با این تفاوت که تابع خرید امن یک آرگومان بیشتر دارد و آن چیزی نیست جز ورودی وب‌هوک پرداخت درون برنامه‌ای. در صورتی که با وب‌هوک پرداخت درون برنامه‌ای آشنایی ندارید، به اینجا مراجعه کنید.

در صورتی که از هر یک از دو روش بالا برای خرید محصول استفاده کنید، نتیجه‌ی خرید محصول از طریق لیسنر زیر به دست شما می‌رسد. در لیسنر زیر حالات مختلف resultCode در حالت خرید امن توضیح داده شده است. دقت کنید که اگر خرید شما غیرامن باشد، دو نتیجه‌ی IapResult.NOT_VERIFIED و IapResult.VERIFICATION_ERROR اصلا رخ نخواهند داد. اما سایر حالات خطا ممکن هستند.

public void OnPurchaseFinished(IapResult result, Purchase purchase, string webhookMessage) {
    Debug.Log("Purchase finished.");
    Debug.Log("request code: " + result.RequestCode);   // 5001 for secure,
                                                      // 6001 for insecure
    Debug.Log("result code: " + result.ResultCode
                 + " and message: " + result.Message);  // All result codes are
                                              // found in IapResult source code.
    if (result.ResultCode == IapResult.SUCCESS) {
        Debug.Log("sku:" + purchase.ProductId +
                  ", purchaseToken: " + purchase.PurchaseToken);
        Debug.Log("webhook response: " + webhookMessage);
    } else if (result.ResultCode == IapResult.NOT_VERIFIED) {
        Debug.Log("Oh, no! A purchase forgery has been occurred.\n" +
                     "You can perform appropriate actions due to this immorality.\n" +
                     "For example, ban the user for some days, etc.");
    } else if (result.ResultCode == IapResult.VERIFICATION_ERROR) {
        Debug.Log("Some server error. :(\n" +
                     "You'd better trust the purchase " +
                     "to avoid potential customers dissatisfaction.");
    } else {
        // Failed for some reason.
        // `purchase` and `webhookMessage` would be null.
    }
}

مصرف محصول

بعد از این‌که یک محصول درون برنامه‌ای را می‌خرید، می‌توانید آن را مصرف کنید. در صورتی که محصول خریداری شده را مصرف نکنید، کافه‌بازار اجازه‌ی خرید مجدد آن محصول را به شما نمی‌دهد و همواره این محصول در لیست محصولات خریداری شده قابل دریافت است. به عنوان نمونه، خرید حالت اعلا (premium) یک برنامه از محصولات خریدنی غیرمصرفی محسوب می‌شود؛ زیرا فقط یک‌بار باید قابل خریدن بوده و همیشه باید در لیست خریدها موجود باشد.

مشابه خرید محصول، مصرف آن نیز دو حالت امن و غیر امن دارد. در کد زیر این دو حالت را مشاهده می‌کنید:

public void OnPurchaseFinished(IapResult result, Purchase purchase, string webhookMessage) {

    // If you want to consume this purchase,
    // the following code will help.
    if (result.ResultCode == IapResult.SUCCESS) {
        string sku = purchase.ProductId;
        string purchaseToken = purchase.PurchaseToken;
        backtoryIap.InsecureConsume(sku, purchaseToken);
        // Or
        backtoryIap.SecureConsume(sku, purchaseToken, "<webhook-metaData>");
    }
}

نتیجه‌ی خرید محصول نیز مطابق انتظار از طریق لیسنر زیر قابل دسترسی خواهد بود.

public void OnConsumptionFinished(IapResult result,
    string sku, string purchaseToken, string webhookMessage) {
    Debug.Log("Consumption finished.");
    Debug.Log("request code: " + result.RequestCode);   // 5002 for secure,
                                                      // 6002 for insecure
    Debug.Log("result code: " + result.ResultCode
                 + " and message: " + result.Message);  // All result codes are
                                              // found in IapResult source code.
    if (result.ResultCode == IapResult.SUCCESS) {
        Debug.Log("sku: " + sku + ", token: " + purchaseToken);
        Debug.Log("webhook response: " + webhookMessage);
    } else if (result.ResultCode == IapResult.NOT_VERIFIED) {
        Debug.Log("Oh, no! A consume forgery has been occurred.\n" +
                     "You can perform appropriate actions due to this immorality.\n" +
                     "For example, ban the user for some days, etc.");
    } else if (result.ResultCode == IapResult.VERIFICATION_ERROR) {
        Debug.Log("Some server error. :(\n" +
                     "You'd better trust the consumption " +
                     "to avoid potential customers dissatisfaction.");
    } else {
        // Failed for some reason.
        // `sku`, `purchaseToken` and `webhookMessage` would be null.
    }
}

خرید اشتراک

خرید کالای اشتراکی نیز بسیار مشابه خرید کالای خریدنی است و به کمک دو متد زیر انجام می‌گیرد:

backtoryIap.InsecureSubscribe("infinite_gas", "<Developer-Payload>");
backtoryIap.SecureSubscribe("infinite_gas", "<Developer-Payload>", "<webhook-metaData>");

و پاسخ آن در لیسنر زیر قابل دریافت است:

public void OnSubscriptionFinished(IapResult result, Purchase purchase, string webhookMessage) {
    Debug.Log("Purchase finished.");
    Debug.Log("request code: " + result.RequestCode);   // 5003 for secure,
                                                      // 6003 for insecure
    Debug.Log("result code: " + result.ResultCode
                 + " and message: " + result.Message);  // All result codes are
                                              // found in IapResult source code.
    if (result.ResultCode == IapResult.SUCCESS) {
        Debug.Log("sku:" + purchase.ProductId +
                  ", purchaseToken: " + purchase.PurchaseToken);
        Debug.Log("webhook response: " + webhookMessage);
    } else if (result.ResultCode == IapResult.NOT_VERIFIED) {
        Debug.Log("Oh, no! A purchase forgery has been occurred.\n" +
                     "You can perform appropriate actions due to this immorality.\n" +
                     "For example, ban the user for some days, etc.");
    } else if (result.ResultCode == IapResult.VERIFICATION_ERROR) {
        Debug.Log("Some server error. :(\n" +
                     "You'd better trust the purchase " +
                     "to avoid potential customers dissatisfaction.");
    } else {
        // Failed for some reason.
        // `purchase` and `webhookMessage` would be null.
    }
}

در هر سه عمل خرید، مصرف یا اشتراک که در بالا دیدیم، در صورتی که لیسنر برای درخواست غیر امن صدا شده باشد، پاسخ وب‌هوک طبیعتا وجود ندارد و webhookMessage الزاما null خواهد بود.

کدهای درخواست

تمامی کدهای درخواست عملیات‌های بالا از طریق کدهای زیر قابل رؤیت و ویرایش هستند. در صورتی که این کدها با کد درخواست یک کتاب‌خانه‌ی دیگر یا جایی از کدهای خود شما تداخل دارند، می‌توانید آن‌ها را تغییر دهید.

Debug.Log("request codes:\n" +
        BacktoryIap.SECURE_PURCHASE_REQUEST_CODE + "\n" +   // 5001
        BacktoryIap.SECURE_CONSUME_REQUEST_CODE + "\n" +    // 5002
        BacktoryIap.SECURE_SUBSCRIBE_REQUEST_CODE + "\n" +  // 5003
        BacktoryIap.INSECURE_PURCHASE_REQUEST_CODE + "\n" + // 6001
        BacktoryIap.INSECURE_CONSUME_REQUEST_CODE + "\n" +  // 6002
        BacktoryIap.INSECURE_SUBSCRIBE_REQUEST_CODE);       // 6003

// Edit a request code
BacktoryIap.SECURE_PURCHASE_REQUEST_CODE = 7001;