مقدمه
خرید درونبرنامهای مایکت API سادهای برای شما فراهم کرده است تا به راحتی بتوانید امور مربوط به «خرید» را مدیریت کنید. این نوشته تمام نیازمندیهای شما برای پیادهسازی خرید درون برنامهای در کد اندروید را شرح میدهد، هر چند ممکن است برای آشنایی بیشتر با مفاهیم و مراحل فرآیند خرید درون برنامهای، نیاز باشد سایر مستندات این پایگاه دانش را مطالعه کنید.
آشنایی با مدلهای پیادهسازی محصولات درونبرنامهای
سه مدل کلی برای کسب درآمد از طریق خرید درونبرنامهای وجود دارد: محصولات مصرفشدنی، مصرفنشدنی و اشتراکی.
محصولات مصرفشدنی، محصولاتی هستند که کاربر پس از استفاده از آنها نسبت به خرید مجدد این محصولات اقدام میکند. مثال اینگونه محصولات، سکه، GEM و… است.
محصولات مصرفنشدنی، محصولاتی هستند که کاربران یک بار برای همیشه مبلغی بابت تهیه آنها پرداخت میکنند. مانند باز کردن مراحلی از بازی، حذف تبلیغات و تهیه اکانت پریمیوم.
محصولات اشتراکی، برای مدت زمان محدودی در اختیار کاربران قرار میگیرند. مثلا اشتراک یک ماهه یک برنامه موسیقی یا اشتراک یک ساله یک کتابخوان دیجیتالی. در این مدل، مدت زمان مجاز برای استفاده از برنامه، توسط استور کنترل میشود و نیازی به هیچ پیادهسازی جهت کنترل زمان نیست. شایان ذکر است در حال حاضر مدل اشتراکی در مایکت پشتیبانی نمیشود.
بارگذاری برنامه در پنل توسعهدهندگان
برای شروع باید یک حساب توسعهدهنده بسازید و برنامه خود را در پنل توسعهدهندگان تعریف کنید. سپس، کافی است کلید رمز عمومی برنامه خود را دریافت و محصولات خود را تعریف نمایید. توجه کنید که برای اضافه کردن برنامه به پنل توسعهدهندگان مایکت نیازی نیست حتما apk برنامه خود را بارگذاری کنید. تنها کافی است، در قسمت اضافه کردن برنامه یک شناسه (Packagename) برای برنامه خود رزرو کنید.
دریافت کتابخانه پرداخت
در این پروژه این امکان وجود دارد که با استفاده از Build Variantهای Gradle بتوانید به سرویس پرداخت چند Store (کافهبازار، گوگلپلی و…) متصل شوید. این امکان به شما کمک میکند تا فرایند نگهداری سورس-کد و انتشار پروژه را برای چند فروشگاه اندرویدی، راحتتر کنید.
برای مشاهده و دریافت برنامه نمونه و سورس-کد آن میتوانید به اینجا مراجعه کنید.
برای پیادهسازی پرداخت درونبرنامهای مایکت با این روش، کافی است مراحل زیر را دنبال کنید:
۱. در فایل build.gradle موجود در پوشه اصلی پروژه خود Jitpack Repository را اضافه کنید:
allprojects { repositories { ... maven { url 'https://jitpack.io' } } }
۲. در فایل build.gradle پروژه خود کتابخانه پرداخت مایکت را اضافه کنید:
dependencies { ... implementation 'com.github.myketstore:myket-billing-client:1.6' }
۳. این تکه کد را در قسمت android موجود در فایل build.gradle پروژه خود اضافه کنید:
android { defaultConfig { def marketApplicationId = "ir.mservices.market" def marketBindAddress = "ir.mservices.market.InAppBillingService.BIND" manifestPlaceholders = [marketApplicationId: "${marketApplicationId}", marketBindAddress : "${marketBindAddress}", marketPermission : "${marketApplicationId}.BILLING"] buildConfigField "String", "IAB_PUBLIC_KEY", "\"{MYKET_PUBLIC_KEY}\"" } ... }
توجه کنید، در صورتی که از build.gradle.kts استفاده میکنید، از تکه کد زیر استفاده کنید:
android { defaultConfig { val marketApplicationId = "ir.mservices.market" val marketBindAddress = "ir.mservices.market.InAppBillingService.BIND" manifestPlaceholders.apply { this["marketApplicationId"] = marketApplicationId this["marketBindAddress"] = marketBindAddress this["marketPermission"] = "${marketApplicationId}.BILLING" } buildConfigField( "String", "IAB_PUBLIC_KEY", "YOUR_KEY" ) } ... }
در صورتی که میخواهید به غیر از سرویس پرداخت درونبرنامهای مایکت به سرویس پرداخت استورهای دیگر متصل شوید، کافی است به جای defaultConfig از productFlavors استفاده کنید:
android { flavorDimensions "store" productFlavors { myket { dimension "store" def marketApplicationId = "ir.mservices.market" def marketBindAddress ="ir.mservices.market.InAppBillingService.BIND" manifestPlaceholders = [marketApplicationId: "{marketApplicationId}", marketBindAddress: "${marketBindAddress}", marketPermission: "${marketApplicationId}.BILLING"] buildConfigField "String", "IAB_PUBLIC_KEY", “\"{MYKET_PUBLIC_KEY}\"" } otherMarket { dimension "store" def marketApplicationId = "{OTHER_MARKET_PACKAGE_NAME}" def marketBindAddress = "{OTHER_MARKET_BIND_ADDRESS}" manifestPlaceholders = [marketApplicationId: "${marketApplicationId}", marketBindAddress : "${marketBindAddress}", marketPermission : "{OTHER_MARKET_PERMISSION}"] buildConfigField "String", "IAB_PUBLIC_KEY", “\”{OTHER_MARKET_PUBLIC_KEY}\"" } } ... }
۴. سپس کلید رمز عمومی (RSA PublicKey) برنامه خود (که از پنل توسعهدهندگان مایکت دریافت کردید) را به جای {MYKET_PUBLIC_KEY} جایگزین کنید:
buildConfigField "String", "IAB_PUBLIC_KEY", "\"{MYKET_PUBLIC_KEY}\""
دقت کنید که قسمت buildConfigField پس از تغییر مشابه زیر شده باشد:
buildConfigField "String", "IAB_PUBLIC_KEY", "\"MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgOW5KR56WBWCb5K+yyVDnh/7op0FY4zmM93CWz3xFhgUJe2WXM/8MgpTHiDxrj2Mkgt9bg30qZDtT8gzDHiTgNv6G7pZBDWuyKEariGbbQgoCoeaq3GBcNsQf418jsvOfPjzZ7Rpcl/+9ZPsp1kbJVOmZxnwAZx/wnkUduwfuf8hAgMBAAE=\""
در صورتی که میخواهید به غیر از مایکت، استور دیگری را اضافه کنید، باید مقادیر {OTHER_MARKET_PACKAGE_NAME}، {OTHER_MARKET_BIND_ADDRESS}، {OTHER_MARKET_PERMISSION} و {OTHER_MARKET_PUBLIC_KEY} را جایگزین کنید. مثلا برای پیادهسازی پرداخت درونبرنامهای برای استور مایکت و کافهبازار، productFlavors به صورت زیر است:
android { flavorDimensions "store" productFlavors { myket { dimension "store" def marketApplicationId = "ir.mservices.market" def marketBindAddress ="ir.mservices.market.InAppBillingService.BIND" manifestPlaceholders = [marketApplicationId: "{marketApplicationId}", marketBindAddress: "${marketBindAddress}", marketPermission: "${marketApplicationId}.BILLING"] buildConfigField "String", "IAB_PUBLIC_KEY", “\"{MYKET_PUBLIC_KEY}\"" } bazaar { dimension "store" def marketApplicationId = "com.farsitel.bazaar" def marketBindAddress = "ir.cafebazaar.pardakht.InAppBillingService.BIND" manifestPlaceholders = [marketApplicationId: "${marketApplicationId}", marketBindAddress : "${marketBindAddress}", marketPermission : "com.farsitel.bazaar.permission.PAY_THROUGH_BAZAAR"] buildConfigField "String", "IAB_PUBLIC_KEY", “\”{BAZAAR_PUBLIC_KEY}\"" } } ... }
دقت کنید که در مثال بالا باید قسمت {MYKET_PUBLIC_KEY} با کلید رمز عمومی مایکت و قسمت {OTHER_MARKET_PUBLIC_KEY} با کلید رمز عمومی کافهبازار، جایگزین شود. در نهایت میتوانید برای هر فروشگاه یک خروجی (نسخه) مجزا بگیرید.
۵. کتابخانه مایکت به ساختار کد شما اضافه شد. هماکنون کافی است که پروژهٔ خود را Sync و Rebuild کنید.
مراحل پیادهسازی
ساختن IabHelper
برای شروع کار باید یک Instance از IabHelper ایجاد کنیم. با توجه به اینکه در متدها و Callbackهای مختلف نیاز است به این Object دسترسی داشته باشید، بهتر است این Instance را به صورت یک فیلد در Activity خود تعریف کنید. برای ساختن IabHelper نیاز است تا کلید رمز عمومی را (که از پنل توسعهدهندگان دریافت کردهاید) به همراه Context به Constructor کلاس IabHelper بدهید:
mHelper = new IabHelper(context, BuildConfig.IAB_PUBLIC_KEY);
دقت کنید که مقدار IAB_PUBLIC_KEY (کلید رمز عمومی) را باید بعد از Sync شدن پروژه، از BuildConfig بخوانید.
تنظیم Logger
کلاس IabHelper لاگهای خود را با تگ “IabHelper” در خروجی لاگهای اندروید (LogCat) مینویسد. شما میتوانید این لاگها را فعال یا غیر فعال نمایید:
mHelper.enableDebugLogging(true);
اتصال به سرویس مایکت (startSetup)
برای اتصال به سرویس پرداخت درونبرنامهای مایکت کافی است از متد startSetup استفاده کنید. این متد را باید قبل از هر متد دیگری صدا بزنید و در صورت متصل شدن به سرویس میتوانید از سرویس پرداخت درونبرنامهای مایکت استفاده کنید. حال یک Callback از جنس OnIabSetupFinishedListener در پارامتر این متد پیادهسازی کنید:
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { public void onIabSetupFinished(IabResult result) { } }); }
پس از تلاش برای اتصال به سرویس، متد onIabSetupFinished همراه با یک IabResult فراخوانی میشود و نتیجه را اعلام میکند. در صورتی که خروجی متد isSuccess در IabResult برابر TRUE بود، اتصال با موفقیت انجام شده است. در غیر این صورت برنامهٔ شما نتوانسته به سرویس مایکت متصل شود که در این صورت میتوانید از متد getMessage برای علت خطا استفاده کنید.
بهروز کردن اطلاعات خرید(queryInventory)
به محض متصل شدن به سرویس مایکت (یعنی زمانی که متد isSuccess در IabResult برابر TRUE بود) باید خریدهای کاربر را بهروز کنید. بهروز کردن خرید کاربرها در مدلهای محصولات مختلف مفهومهای مختلفی دارد:
برای محصولات مصرفشدنی این معنی را میدهد که به هر علت یک خرید مصرف نشده وجود دارد که باید مصرف شود و به کاربر تحویل داده شود. مثلا فرض کنید که در فرایند خرید قبلی، کاربر با مشکل مواجه میشود یا به هر دلیلی قبل از مصرف کردن محصول، برنامهٔ شما بسته میشود و یا ارتباط کاربر قطع میگردد. در این صورت باید در اجرای بعدی این محصول را مصرف کنید و به کاربر تحول دهید.
علت بهروز کردن محصولات برای محصولات مصرفنشدنی این است که از این طریق متوجه میشوید که کاربر محصول شما را خریداری کرده است یا نه. در صورتی که خریداری کرده بود، محصول یا همان امکان مورد نظر (مثلا حذف تبلیغات یا مراحل کامل بازی یا …) را برای او فعال میکنید.
در مورد محصولات اشتراکی هم از این طریق متوجه میشوید که کاربر هنوز مشترک برنامهٔ شما هست یا خیر. در صورتی که زمان اشتراک تمام نشده بود در queryInventory به شما خبر داده میشود.
در واقع میتوان گفت تمام محصولاتی که مصرف نکردهاید،همیشه در queryInventory برمیگردد. در صورتی که مصرفشدنی بود که باید همانجا مصرف شود و بعد تحویل کاربر شود و در صورتی که مصرفنشدنی یا اشتراکی بود، باید به کاربر تحویل داده شود.
همچنین با استفاده از queryInventory میتوانید اطلاعات مربوط به محصولات خود (نام محصول، توضیحات، قیمت و …) که در پنل توسعهدهندگان وارد نمودهاید را دریافت کنید. مثلا فرض کنید که بعد از انتشار، قیمت یا نام یکی از محصولات خود را تغییر میدهید. با استفاده از این قابلیت میتوانید در UI برنامهٔ خود همیشه مشخصات محصولی را نشان دهید که در پنل توسعهدهندگان مایکت قرار دارد. به عبارتی با تغییر آن، برنامه شما نیز تغییر میکند.
برای بهروز کردن خریدها باید نام محصولات را به همراه یک Callback از جنس QueryInventoryFinishedListener به متد queryInventoryAsync از کلاس IabHelper بدهید تا فرایند بهروز کردن آغاز شود:
try { mHelper.queryInventoryAsync(true, itemSkus, subSkus, mGotInventoryListener); } catch (IabAsyncInProgressException e) { Log.e(TAG, "Error querying inventory. Another async operation in progress."); }
متد queryInventoryAsync دارای چهار پارامتر است که به ترتیب:
۱. boolean querySkuDetails: در صورتی که میخواهید اطلاعات محصولات بهروز شود (نام، قیمت و …) این Boolean را برابر TRUE قرار دهید. توجه کنید در صورتی که نمیخواهید از این قابلیت استفاده کنید این مقدار را برابر FALSE قرار دهید تا سرویس اضافهای صدا نشود.
۲. List<String> moreItemSkus: لیستی از ID محصولات فروشی (مصرفشدنی و مصرفنشدنی). در صورتی که برنامهٔ شما محصولات فروشی ندارد این لیست را برابر NULL قرار دهید.
۳. List<String> moreSubsSkus: لیستی از ID محصولات اشتراکی. در صورتی که برنامه شما محصولات اشتراکی ندارد این لیست را برابر NULL قرار دهید.
۴. QueryInventoryFinishedListener listener: برای جواب این سرویس یک Callback از جنس QueryInventoryFinishedListener تنظیم شود.
نتیجه این درخواست با دو Object با نامهای IabResult و Inventory در متد onQueryInventoryFinished بازمیگردد. از IabResult موفق بودن/نبودن درخواست مشخص میشود و خریدهای بهروز شده و اطلاعات محصولات در Inventory قرار میگیرد. Inventory شامل دو Map با نامهای mSkuMap و mPurchaseMap است. خریدهای بهروز شده در mPurchaseMap قرار میگیرد که با استفاده از متد getPurchase در کلاس Inventory میتوانید متوجه شوید برای محصول مورد نظرتان خرید بهروز شدهای وجود دارد یا خیر. همچنین میتوانید با استفاده از متد getAllPurchases به تمام لیستی (از جنس Purchase) درسترسی پیدا کنید.
همچنین میتوانید اطلاعات خرید که در پنل توسعهدهندگان تنظیم کردهاید را در mSkuMap بیابید. توجه کنید در صورتی که مقدار querySkuDetails را برابر FALSE قرار دهید این Map برابر با NULL خواهد بود. با استفاده از متد getSkuDetails در کلاس Inventory میتوانید به اطلاعات خرید محصول مورد نظر دسترسی پیدا کنید و UI برنامهٔ خود را بهروز نمایید.
نمونهای از QueryInventoryFinishedListener را میتوانید در اینجا ببینید:
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() { public void onQueryInventoryFinished(IabResult result, Inventory inventory) { // Have we been disposed of in the meantime? If so, quit. if (mHelper == null) return; // Is it a failure? if (result.isFailure()) { complain("Failed to query inventory: " + result); return; } // (1) // Do we have the premium upgrade? Purchase premiumPurchase = inventory.getPurchase(SKU_PREMIUM); Log.d(TAG, "User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM")); // (2) // First find out which subscription is auto renewing Purchase gasMonthly = inventory.getPurchase(SKU_INFINITE_GAS_MONTHLY); Purchase gasYearly = inventory.getPurchase(SKU_INFINITE_GAS_YEARLY); // (3) // Check for gas delivery -- if we own gas, we should fill up the tank immediately Purchase gasPurchase = inventory.getPurchase(SKU_GAS); consume(SKU_GAS) updateUi(); Log.d(TAG, "Initial inventory query finished; enabling main UI."); } };
همانطور که در مثال بالا میبینید در قسمت (1) محصول با شناسهٔ SKU_PREMIUM را از خریدهای بهروز شده جستوجو میکند و در صورتی که premiumPurchase برابر با NULL نباشد این محصول مصرفنشدنی به کاربر تحویل میشود.
در قسمت (2) برای محصولات اشتراکی جستوجو انجام میشود و در صورتی که اشتراک ماهیانه یا سالیانه کاربر فعال باشد، مقدار gasMonthly یا gasYearly مخالف NULL خواهد بود.
در قسمت (3) برای محصول مصرفشدنی SKU_GAS جستوجو انجام میشود و در صورت NULL نبودن gasPurchase این محصول مصرف میشود (Consume) و سپس به کاربر تحویل داده خواهد شد. در خصوص Consume در ادامه توضیح خواهیم داد.
فیلدهای آبجکت Purchase به صورت زیر است:
توضیحات | Type | نام |
نوع محصول (inapp/subs) | String | mItemType |
شمارهٔ فاکتور خرید | String | mOrderId |
پکیجنیم برنامهٔ شما | String | mPackageName |
ID محصول | String | mSku |
Timestamp زمان خرید | long | mPurchaseTime |
وضعیت خرید (مصرفشده / مصرف نشده) | int | mPurchaseState |
Developer payload | String | mDeveloperPayload |
توکن خرید این محصول | String | mToken |
رشتهٔ JSON که از سرور مایکت برمیگردد | String | mOriginalJson |
Signiture رشتهٔ mOriginalJson | String | mSignature |
فعال/غیرفعال بودن Auto Renew | boolean | mIsAutoRenewing |
با توجه به اینکه اولین درخواستی که باید بعد از اتصال به سرویس مایکت صدا زده شود در خواست queryInventoryAsync است، بدنه کلی متد startSetup به صورت زیر خواهد بود (با فرض اینکه لیستهای subSkus و itemSkus با مقادیر IDهای محصولات ساخته شده است و mGotInventoryListener نیز پیادهسازی شده است):
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { public void onIabSetupFinished(IabResult result) { Log.d(TAG, "Setup finished."); if (!result.isSuccess()) { // Oh noes, there was a problem. complain("Problem setting up in-app billing: " + result); return; } // Have we been disposed of in the meantime? If so, quit. if (mHelper == null) return; Log.d(TAG, "Setup successful. Querying inventory."); try { mHelper.queryInventoryAsync(true, itemSkus, subSkus, mGotInventoryListener); } catch (IabAsyncInProgressException e) { complain("Error querying inventory. Another async operation in progress."); } } }); }
فرستادن درخواستهای خرید درونبرنامهای
هنگامی که برنامه شما به مایکت متصل شد، شما میتوانید برای محصولات درون برنامه درخواست خرید بفرستید. مایکت یک رابط کاربری برای روند پراخت کاربران فراهم میکند به طوری که برنامه شما درگیر مدیریت تراکنشها نخواهد شد و این قبیل عملیات بر عهده مایکت خواهد بود. وقتی یک محصول خریداری شد، مایکت کاربر را مالک آن میداند و تا زمانی که مصرف نشده، از خرید دوباره آن محصول توسط کاربر جلوگیری میکند (یعنی شناسه محصولهایی که هنوز مصرف نشدند با شناسه محصولاتی که کاربر قصد خرید آنها را دارد، یکی باشد). شما باید نحوه مصرف محصولات در برنامه خود را کنترل کنید و مایکت را برای مصرف مطلع سازید تا محصول دوباره برای خرید مهیا شود. شما همچنین میتوانید به راحتی فهرست محصولاتی که کاربر مالک آنها است را از مایکت بخواهید. این زمانی مفید خواهد بود که مثلا بخواهید هنگام باز شدن برنامه خود بر اساس این محصولات به کاربر سرویس مناسبی ارائه دهید.
فرض کنید که برنامه شما باز شده و با موفقیت به سرویس مایکت متصل گردیده است. کاربر وارد صفحه فروشگاه برنامه میشود و لیستی از محصولات شما را به همراه قیمتهای آنها مشاهده میکند (لیستی که با استفاده از mSkuMap در کلاس Inventory ساختهاید). در کنار هر آیتم لیست، یک دکمه خرید وجود دارد. کاربر با زدن دکمه خرید وارد فرایند خرید آن محصول میشود. شروع فرایند خرید تمامی محصولات درونبرنامهای با استفاده از متد launchPurchaseFlow در کلاس IabHelper، به صورت زیر است:
try { mHelper.launchPurchaseFlow(this, SKU_PREMIUM, mPurchaseFinishedListener, payload); } catch (IabAsyncInProgressException e) { complain("Error launching purchase flow. Another async operation in progress."); }
متد launchPurchaseFlow پارامترهای زیر را به ترتیب میپذیرد:
۱. Activity act: اکتیویتی برنامه شما که فرایند خرید در آن انجام میشود.
۲. String sku: شناسه محصول درونبرنامهای که قصد خرید آن را دارید.
۳. OnIabPurchaseFinishedListener listener: یک Callback از جنس OnIabPurchaseFinishedListener که پایان فرایند خرید را خبر میدهد.
۴. String developerPayload: مقدار Developer payload این خرید. از رشته developerPayload برای مشخص کردن هر اطلاعات اضافی که مایل هستید مایکت به همراه اطلاعات خرید برای شما برگرداند، استفاده میشود.
زمانی که با استفاده از launchPurchaseFlow فرایند خرید را آغاز میکنید، یک Activity از مایکت روی برنامه شما باز میشود و مشخصات محصول به همراه درگاههای خرید را به کاربر نمایش میدهد تا کاربر خرید را انجام دهد.
سپس IabHelper پس از بررسی Signature خرید با کلید رمز عمومی شما، OnIabPurchaseFinishedListener را فراخوانی میکند. در متد onIabPurchaseFinished دو Object با نامهای IabResult و Purchase باز میگردد. IabResult نشان میدهد که خرید موفق انجام شده است یا خیر و در صورت موفقیت، مشخصات محصول خریداری شده در Purchase قرار داده میشود. یک نمونه از پیادهسازی این Callback به صورت زیر است:
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() { public void onIabPurchaseFinished(IabResult result, Purchase purchase) { Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase); // if we were disposed of in the meantime, quit. if (mHelper == null) return; if (result.isFailure()) { complain("Error purchasing: " + result); setWaitScreen(false); return; } Log.d(TAG, "Purchase successful."); // handling purchase } };
پس از اینکه خرید با موفقیت انجام شد، با توجه به مدل محصول، آن را به کاربر تحویل دهید. اگر محصول برنامه شما مصرفنشدنی یا اشتراکی است باید آن را پس از onIabPurchaseFinished موفقیت آمیز به کاربر تحویل دهید. در صورتی که محصول برنامه شما مصرفشدنی است ابتدا باید آن را Consume کنید و سپس به کاربر تحویل دهید. Consume کردن یک محصول باعث میشود که آن محصول، در لیست queryInventory (محصولات بهروز شده) بازنگردد، بنابراین محصولات مصرفنشدنی و اشتراکی را Consume نکنید.
مصرف نمودن یک خرید
هنگامی که یک کاربر محصولی را خریداری میکند، مالک آن تلقی میشود و امکان خرید دوباره آن را نخواهد داشت. شما باید یک درخواست مصرف به مایکت ارسال کنید تا مایکت اجازه خرید دوباره محصول را به کاربر بدهد.
برای Consume کردن یک محصول، از متد consumeAsync به همراه یک Purchase و یک Callback از جنس OnConsumeFinishedListener استفاده کنید:
try { mHelper.consumeAsync(purchase, mConsumeFinishedListener); } catch (IabAsyncInProgressException e) { complain("Error consuming gas. Another async operation in progress."); }
یک نمونه از پیادهسازی OnConsumeFinishedListener به صورت زیر است:
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() { public void onConsumeFinished(Purchase purchase, IabResult result) { Log.d(TAG, "Consumption finished. Purchase: " + purchase + ", result: " + result); // if we were disposed of in the meantime, quit. if (mHelper == null) return; if (result.isSuccess()) { // successfully consumed, so we apply the effects of the item in our // game world's logic, which in our case means filling the gas tank a bit Log.d(TAG, "Consumption successful. Provisioning."); } else { complain("Error while consuming: " + result); } Log.d(TAG, "End consumption flow."); } };
در متد onConsumeFinished دقیقا مانند onIabPurchaseFinished، یک Purchase برای مشخصات خرید و یک IabResult برای نتیجهٔ Consume برمیگردد. در صورتی که isSuccess برابر TRUE بود، میتوانید محصول را به کاربر تحویل دهید (مثلا سکهٔ او را افزایش دهید).
این مسئولیت شماست که مزایای هر محصول را به کاربر ارائه دهید. به عنوان مثال اگر یک کاربر در بازی شما پول یا سکه خریداری کند، شما باید مقدار پول یا تعداد سکه او را بروز رسانی نمایید.
محصولات اشتراکی
مایکت از اشتراک پشتیبانی نمیکند. برای اطلاعات بیشتر در این زمینه میتوانید از تیم پشتیبانی مایکت به آدرس ایمیل developer@myket.ir کمک بگیرید.
ارسال اطلاعات خرید به سرور
در صورتی که تحویل محصولات در منطق برنامه شما سمت سرور انجام میشود، لازم است که اطلاعات خرید و کاربر خود را به سرور ارسال کنید. توجه کنید که ابتدا باید محصول را در Backend سیستم خود، به کاربر تحویل دهید، سپس متد Consume را صدا بزنید که مایکت از تحویل محصول به کاربر مطمئن شود. انجام دهید. برای این کار کافی است که اطلاعات خرید را با استفاده از متد getOriginalJson در کلاس Purchase به سرور ارسال کنید. توجه کنید که فیلد token در این Json برای تمام خریدهای برنامه شما یکتا است و میتوانید به عنوان کلید از آن استفاده کنید. همچنین در نظر داشته باشید که لازم است حتما در کنار این Json مقدار Signature را نیز (با استفاده از متد getSignature در کلاس Purchase) برای سرور ارسال کنید و با استفاده از کلید رمز عمومی که از پنل توسعهدهندگان مایکت دریافت نمودهاید، با هم تطبیق دهید.
آمادهسازی جهت بستن برنامه (onDestroy)
در onDestroy اکتیویتی برنامه خود که یک Instance از IabHelper را در آن نگه داشتهاید، کد زیر را بنویسید:
@Override public void onDestroy() { super.onDestroy(); Log.d(TAG, "Destroying helper."); if (mHelper != null) { mHelper.disposeWhenFinished(); mHelper = null; } }
برنامه آموزشی TrivialDrive
برنامه آموزشی TrivialDrive توسط گوگل طراحی شده است که ما با تغییرات آن را به سرویس پرداخت درونبرنامهای مایکت متصل کردهایم. سورس این برنامه را میتوانید در Github مشاهده نمایید.
در این برنامه چهار دکمه وجود دارد. با استفاده از دکمه سبز رنگ (Drive) میتوانید بازی کنید و مشاهده نمایید که شاخص بنزین شما کم میشود. در صورتی که بنزین شما تمام شود دیگر نمیتوانید بازی کنید و مجبور به خرید بنزین میشوید. با دکمه زرد رنگ (Buy GAS) میتوانید بنزین بخرید. توجه کنید بنزین در این مثال یک محصول مصرفشدنی است. با استفاده از دکمه آبی رنگ (Upgrade my Car) میتوانید محصول مصرفنشدنی بخرید و برای همیشه بنزین داشته باشید و با استفاده از دکمه قرمز رنگ (Get Infinite GAS) میتوانید اشتراک یک ماهه یا یک ساله بخرید.
ملاحظات امنیتی
برای جلوگیری از دسترسی غیر مجاز به امکانات غیر رایگان برنامهٔ شما لازم است که موارد امنیتی زیر را در نظر بگیرید.
نگهداری از محتوا
سعی کنید از سروری برای نگهداری محتوای غیر رایگان برنامه خود استفاده کنید و پس از خرید کاربر با استفاده از وبسرویس آن را به کلاینت خود منتقل کنید. همچنین پس از دریافت محتوا از سرور، آن را به صورت رمز شده در حافظه دستگاه نگه دارید و از کلیدی منحصر به همان دستگاه برای دسترسی به اطلاعات رمز شده استفاده کنید. در نظر داشته باشید که در صورتی که محتوای برنامهٔ شما درون فایل apk باشد، امن نیست و میتوان به آن دسترسی داشت.
تایید امضای دیجیتالی روی سرور
برای فرستادن اطلاعات به سِرور برنامه خود، حتما رشته Json را به همراه Signature دریافتی از مایکت (با استفاده از متد getSignature و getOriginalJson در کلاس Purchase) ارسال کنید و فرایند تایید امضای دیجیتال را با کلید رمز عمومی (PublicKey) را در سِرور نیز انجام دهید. توجه کنید فرایند تایید امضای دیجیتال در متد verifyPurchase در کلاس Security انجام میشود و دقیقا مانند همین کار را باید در سرور خود نیز انجام دهید:
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) { if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) || TextUtils.isEmpty(signature)) { Log.e(TAG, "Purchase verification failed: missing data."); return false; } PublicKey key = Security.generatePublicKey(base64PublicKey); return Security.verify(key, signedData, signature); }
به هم ریختگی کدها
برای جلوگیری از عملکرد برنامههای مخرب، حتما از سورس کد برنامهٔ خود محافظت کنید. ابتدا باید در کد خود بهمریختگی (obfuscation) ایجاد کنید. برای این کار میتوانید از ابزارهایی نظیر ProGuard استفاده کنید. همچنین مقادیر حساس نظیر کلید رمز عمومی (Public key) را به صورت رشتهای ثابت در کد خود قرار ندهید و سعی کنید این رشتهها را در زمان اجرای برنامه بسازید (مانند XOR با چند رشته دیگر). توجه کنید سورس کد کمکی که در اختیار شما قرار دارد میتواند در اختیار هکرها نیز قرار گیرد بنابراین بهتر است ساختار پروژه و ترتیب متدها را تغییر دهید تا برنامه شما الگوی متفاوتی داشته باشد و پیدا کردن آن زمانبَر شود.
استفاده از Developer payload
در سرویس پرداخت درونبرنامهای مایکت میتوانید همراه درخواست خرید یک رشته، موسوم به developer payload هم ارسال کنید. این رشته میتواند به عنوان یک شناسه منحصر به فرد از سمت شما برای این خرید در نظر گرفته شود. بعد از اتمام مراحل خرید این رشته را در کلاس Purchase به برنامهٔ شما بازمیگرداند. همچنین زمانی که خریدهای خود را بهروز میکنید، مایکت این رشته را نیز همراه دیگر جزئیات خرید برمیگرداند.
شما باید توکن رشتهای استفاده کنید که به برنامه شما در تشخیص کاربری که خرید انجام داده است کمک کند. به این ترتیب بعداً میتوانید متوجه شوید که خرید مورد نظر برای کاربر معتبر است یا خیر. برای محصولات مصرفشدنی این رشته میتواند کاملاً تصادفی باشد. اما برای محصولات مصرفنشدنی یا اشتراکی، برای اطمینان از صحت خریده شدن محصول توسط کاربر باید از رشتهای استفاده کنید که منحصراً آن فرد را شناسایی میکند.
وقتی که پاسخ را از مایکت دریافت کردید، مطمئن شوید رشته developer payload که همراه با جزئیات خرید به شما بازگردانده است، همانی است که شما برای شروع عملیات پرداخت ارسال کرده بودید. برای اطمینان از امنیت بیشتر پیشنهاد میشود این عملیات اعتبارسنجی را بر روی سِرور خود انجام دهید.
بررسی همیشگی مجوز دسترسی به محتوای برنامه
برای امنیت بیشتر پیشنهاد میشود که برای محصولات مصرفنشدنی و اشتراکی، همیشه با باز شدن برنامه، مجوز دسترسی را از مایکت بپرسید و آن را در هیچجا ذخیره نکنید.
تست پیش از انتشار
برای تست سناریوهای خرید درونبرنامهای مایکت تنها کافی است که در پنل توسعهدهندگان، Packagename برنامه خود را رزرو کنید یا یک نسخه از apk برنامه را بارگذاری نمایید که به کلید رمز عمومی برنامه خود دسترسی داشته باشید. سپس یک یا چند محصول درونبرنامهای جهت تست با قیمت بسیار کم (مثلا ۱۰۰ ریال) ایجاد کنید و سناریوهای مختلف را تست نمایید. در نظر داشته باشید برای تست خرید درونبرنامهای نیاز به انتشار برنامه یا تایید مدیر ندارید.