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

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

پیش‌نیازها

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

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

پس از راه‌اندازی SDK بکتوری در اندروید، در شروع اپلیکیشن بایستی SDK بکتوری را initialize کنید. در عمل initialize، شما تمامی سرویس‌هایی که علاقمند به استفاده هستید را ذکر خواهید کرد. برای سرویس درون پرداخت امن شما فقط کافیست که کلید مرکز بازی را برای SDK فراهم کنید. در نتیجه کد زیر مورد استفاده قرار می‌گیرد:

// Extending android application to initialize backtory
public class MainApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        // Initializing backtory
        BacktoryClient.init(KeyConfiguration.newBuilder().
            setAuthKeys("<X-Backtory-Authentication-Id>",
                        "<X-Backtory-Authentication-Key (Client)>").
            // Enabling Game Center
            setGameKey("<X-Backtory-Game-Id>").
            // Finalizing sdk
            build(), this);
    }
}

اضافه کردن کتاب‌خانه‌ی پرداخت درون برنامه‌ای

با توجه به این‌که کتاب‌خانه‌ی پرداخت درون برنامه‌ای از SDK اصلی بکتوری جداست، می‌بایست که این dependency را به صورت جداگانه از SDK اصلی بکتوری در فایل build.gradle ماژول برنامه‌تان (به صورت پیش‌فرض به نام app) اضافه کنید. به صورت زیر:

compile 'com.pegah.backtory:backtory-iap:0.6.7' // put the latest version here

دقت کنید که شماره‌ی نسخه‌ی کتاب‌خانه‌ی backtory-iap در فایل build.gradle شما باید دقیقا برابر با شماره‌ی نسخه‌ی SDK اندروید بکتوری باشد.

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

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

public class MyActivity extends Activity implements BacktoryIapListener {

    private BacktoryIap backtoryIap;

    public void init() {
        backtoryIap = new BacktoryIap(this, "<CafeBazaar-RSA-Key>", this);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        backtoryIap.handleActivityResult(requestCode, resultCode, data);
    }

    @Override
    protected void onDestroy() {
        if (backtoryIap != null)
            backtoryIap.dispose();
        super.onDestroy();
    }

    @Override
    public void onGetSkuDetailsFinished(IapResult result, ArrayList<SkuDetails> skuDetailsList) {

    }

    @Override
    public void onGetPurchasesFinished(IapResult result, ArrayList<String> ownedSkus, String continuationToken) {

    }

    @Override
    public void onPurchaseFinished(IapResult result, Purchase purchase, String webhookMessage) {

    }

    @Override
    public void onConsumptionFinished(IapResult result, String sku, String purchaseToken, String webhookMessage) {

    }

    @Override
    public void onSubscriptionFinished(IapResult result, Purchase purchase, String webhookMessage) {

    }
}

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

  1. ابتدا کلاس MyActivity که از کلاس Activity اندروید ارث‌بری کرده و واسط BacktoryIapListener را پیاده‌سازی می‌کند، ایجاد شده است. در نتیجه توانسته‌ایم این کلاس را به عنوان آرگومان‌های ورودی Constructor شیء پرداخت درون برنامه‌ای پاس دهیم.
  2. یک نمونه از شیء پرداخت درون برنامه‌ای در تابع init ایجاد شده است. شما باید تابع init را در یکی از مقاطع ابتدایی ایجاد اکتیویتی، مثلا در onCreate صدا بزنید.
  3. در تابع onActivityResult متعلق به Activity فوق، تابع backtoryIap.handleActivityResult صدا زده شده است. این به خاطر آن است که پاسخ intent های شما به کافه‌بازار بتواند توسط backtoryIap مدیریت شود.
  4. حتما در تابع onDestory اکتیویتی خود مطابق بالا تابع ()backtoryIap.dispose را صدا بزنید. در صورتی که این کار را نکنید، اتصال به سرویس کافه‌بازار باز می‌ماند و این اتصال باز باعث ضعیف شدن کارایی دستگاه کاربر و مصرف بیهوده‌ی حافظه‌ی آن می‌شود.
  5. توابع مربوط به لیسنر BacktoryIapListener همگی پیاده‌سازی شده اند که گام‌به‌گام به توضیح آن‌ها خواهیم پرداخت.

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

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

ArrayList<String> skuList = new ArrayList<>();
skuList.add("gas");         // 1st sku to get details about
skuList.add("premium");     // 2nd sku
backtoryIap.getSkuDetailsInBackground(skuList);

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

  @Override
  public void onGetSkuDetailsFinished(IapResult result, ArrayList<SkuDetails> skuDetailsList) {
    Log.d("TAG", "getSkuDetails finished.");
    Log.d("TAG", "request code: " + result.getRequestCode());   // It is always -1,
                                                    // which means NO request code!
    Log.d("TAG", "result code: " + result.getResultCode()
        + " and message: " + result.getMessage());              // All result codes are
                                                      // found in IapResult source code.
    if (result.getResultCode() == IapResult.SUCCESS) {
      for(SkuDetails sd : skuDetailsList) {
        Log.d("TAG", "productId: " + sd.getProductId() + ", price: " + sd.getPrice());
      }
    } 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

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

  @Override
  public void onGetPurchasesFinished(IapResult result, ArrayList<String> ownedSkus, String continuationToken) {
    Log.d("TAG", "getPurchases finished.");
    Log.d("TAG", "request code: " + result.getRequestCode());   // It is always -1,
                                                    // which means NO request code!
    Log.d("TAG", "result code: " + result.getResultCode()
        + " and message: " + result.getMessage());              // All result codes are
                                                      // found in IapResult source code.
    if (result.getResultCode() == IapResult.SUCCESS) {
      Log.d("TAG", "Owned product-ids are:");
      for(String os : ownedSkus) {
        Log.d("TAG", 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.
    }

  }

خرید محصول

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

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

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

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

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

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

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

  @Override
  public void onPurchaseFinished(IapResult result, Purchase purchase, String webhookMessage) {
    Log.d("TAG", "Purchase finished.");
    Log.d("TAG", "request code: " + result.getRequestCode());   // 5001 for secure,
                                                                // 6001 for insecure
    Log.d("TAG", "result code: " + result.getResultCode()
        + " and message: " + result.getMessage());              // All result codes are
                                                      // found in IapResult source code.
    if (result.getResultCode() == IapResult.SUCCESS) {
      Log.d("TAG", purchase.toJSONObject().toString());
      Log.d("TAG", "webhook response: " + webhookMessage);
    } else if (result.getResultCode() == IapResult.NOT_VERIFIED) {
      Log.d("TAG", "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.getResultCode() == IapResult.VERIFICATION_ERROR) {
      Log.d("TAG", "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) یک برنامه از محصولات خریدنی غیرمصرفی محسوب می‌شود؛ زیرا فقط یک‌بار باید قابل خریدن بوده و همیشه باید در لیست خریدها موجود باشد.

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

  @Override
  public void onPurchaseFinished(IapResult result, Purchase purchase, String webhookMessage) {

    // If you want to consume this purchase,
    // the following code will help.
    if (result.getResultCode() == IapResult.SUCCESS) {
      String sku = purchase.getProductId();
      String purchaseToken = purchase.getPurchaseToken();
      backtoryIap.insecureConsume(sku, purchaseToken);
      // Or
      backtoryIap.secureConsume(sku, purchaseToken, "<webhook-metaData>");
    }
  }

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

  @Override
  public void onConsumptionFinished(IapResult result,
        String sku, String purchaseToken, String webhookMessage) {
    Log.d("TAG", "Consumption finished.");
    Log.d("TAG", "request code: " + result.getRequestCode());   // 5002 for secure,
                                                                // 6002 for insecure
    Log.d("TAG", "result code: " + result.getResultCode()
        + " and message: " + result.getMessage());              // All result codes are
                                                      // found in IapResult source code.
    if (result.getResultCode() == IapResult.SUCCESS) {
      Log.d("TAG", "sku: " + sku + ", token: " + purchaseToken);
      Log.d("TAG", "webhook response: " + webhookMessage);
    } else if (result.getResultCode() == IapResult.NOT_VERIFIED) {
      Log.d("TAG", "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.getResultCode() == IapResult.VERIFICATION_ERROR) {
      Log.d("TAG", "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(this, "infinite_gas", "<Developer-Payload>");
backtoryIap.secureSubscribe(this, "infinite_gas", "<Developer-Payload>", "<webhook-metaData>");

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

  @Override
  public void onSubscriptionFinished(IapResult result, Purchase purchase, String webhookMessage) {
    Log.d("TAG", "Subscription finished.");
    Log.d("TAG", "request code: " + result.getRequestCode());   // 5003 for secure,
                                                                // 6003 for insecure
    Log.d("TAG", "result code: " + result.getResultCode()
            + " and message: " + result.getMessage());          // All result codes are
                                                      // found in IapResult source code.
    if (result.getResultCode() == IapResult.SUCCESS) {
      Log.d("TAG", purchase.toJSONObject().toString());
      Log.d("TAG", "webhook response: " + webhookMessage);
    } else if (result.getResultCode() == IapResult.NOT_VERIFIED) {
      Log.d("TAG", "Oh, no! A subscribe 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.getResultCode() == IapResult.VERIFICATION_ERROR) {
      Log.d("TAG", "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 خواهد بود.

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

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

Log.d("TAG", "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;