کافهبازار واسط ساده و کارایی برای مدیریت پرداخت درونبرنامهای ارائه میدهد. اطلاعات زیر نحوهٔ ارسال درخواست از برنامهٔ شما به سرویس پرداخت درونبرنامهای توسط API را نشان میدهد.
توجه: برای پیادهسازی کامل و یادگیری چگونگی تست پرداختهای درونبرنامهای پروژهٔ TrivalDrive را مشاهده کنید. این پروژهٔ مثال کاملی از پیادهسازی پرداخت درونبرنامهای ارائه میکند که شامل کلاسهایی برای انجام وظایف کلیدی مربوط به برقراری اتصالال، فرستادن درخواست پرداخت، پردازش پاسخ در بازار و مدیریت موازی کارهای پسزمینهای است تا شما بتوانید فراخوانی متدهای پرداخت درونبرنامهای را از activity اصلی خود انجام دهید.
قبل از آغاز، بخش نگاه کلی را به دقت بخوانید تا با مفاهیم کلی پرداخت درونبرنامهای آشنا شوید که پیادهسازی آن برایتان آسانتر شود.
برای پیادهسازی پرداخت درونبرنامهای در برنامهٔ خود، مراحل زیر را طی کنید:
فایل Android Interface Definition Language (AIDL) اینترفیسی برای سرویس پرداخت درونبرنامهای بازار است. این فایل را میتوانید در پروژهٔ TrivialDrive پیدا کنید. وقتی شما این فایل را به پروژهٔ خود اضافه میکنید، محیط توسعهٔ اندروید شما، یک فایل اینترفیس (IInAppBillingService.java
) میسازد. شما میتوانید از این اینترفیس استفاده کنید تا درخواست پرداخت را با استفاده از فراخوانیهای IPC بسازید.
افزودن کتابخانهٔ پرداخت درونبرنامهای به پروژه:
IInAppBillingService.aidl
را در پروژهٔ اندروید خود کپی کنید.
IInAppBillingService.aidl
را در دایرکتوری /src
import کنید. Eclipse به طور خودکار فایل اینترفیس را بعد از اینکه پروژهٔ خود را build کردید در پروژه شما میسازد.IInAppBillingService.aidl
را در این پوشه کپی کنید. فایل AIDL را در پروژه خود قرار داده و از ابزار Ant برای build کردن پروژه خود استفاده کنید. پس از آن فایل IInAppBillingService.java
ساخته خواهد شد.IInAppBillingService.java
در پوشه /gen پروژه خود داشته باشید.پرداختهای درونبرنامهای از طریق برنامهٔ اندرویدی بازار انجام میشوند، که تمامی ارتباطات بین برنامه شما و سرور بازار را مدیریت میکند. برای استفاده از برنامهٔ بازار، برنامهٔ شما باید دسترسی زیر را درخواست کند. اگر برنامهٔ شما دسترسی پرداخت درونبرنامهای را درخواست نکرده باشد، اما تلاش برای فرستادن درخواست کند، درخواستش رد شده و برنامهتان با خطا مواجه میشود.
برای اینکه به برنامهٔ خود دسترسیهای موردنیاز را بدهید، کد زیر را به فایل AndroidManifest.xml
اضافه کنید:
<uses-permission android:name="com.farsitel.bazaar.permission.PAY_THROUGH_BAZAAR"></uses-permission>
برنامهٔ شما باید از طریق یک ServiceConnection
با بازار ارتباط برقرار کند:
IInAppBillingService
برای برقراری ارتباط با سرویس پرداخت درونبرنامهای بازار، ServiceConnection
را برای اتصال activity خود به IInAppBillingService
پیادهسازی کنید.
متدهای onServiceDisconnected
و onServiceConnected
را override کنید تا پس از برقراری اتصال به نمونهای از IInAppBillingService
ریفرنسی داشته باشید.
IInAppBillingService mService; ServiceConnection mServiceConn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { mService = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = IInAppBillingService.Stub.asInterface(service); } };
در متد onCreate
از activity خود، اتصال را با فراخوانی متد bindService
برقرار کنید. به این متد، دو ورودی بدهید: یکی Intent
ای که رفرنسی به سرویس پرداخت درونبرنامهای دارد و دیگری نمونهای از ServiceConnection
ای که ایجاد نمودهاید.
شما اکنون میتوانید از رفرنسی که در mService ساختهشدهاست، برای ارتباط با سرویس بازار استفاده کنید.
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindService(new Intent("ir.cafebazaar.pardakht.InAppBillingService.BIND"), mServiceConn, Context.BIND_AUTO_CREATE);
مهم: به یاد داشته باشید که اتصال از سرویس پرداخت درونبرنامهای را زمانی که activity تان را میبندید، قطع کنید. اگر اتصال را قطع نکنید، اتصال باز به سرویس باعث تنزل کارایی دستگاه کاربر میشود. مثال زیر طریقهٔ قطع کردن اتصال در سرویس پرداخت درونبرنامهای با نام mServiceConn
را نشان میدهد که با overrideکردن متد onDestroy
در activity انجام میشود.
@Override public void onDestroy() { super.onDestroy(); if (mServiceConn != null) { unbindService(mServiceConn); } }
برای پیادهسازی کامل اتصال به یک سرویس که به IInAppBillingService
متصل میشود، پروژهٔ TrivalDrive را مشاهده کنید.
وقتی برنامهٔ شما به بازار متصل شد، شما میتوانید درخواست خرید را برای محصولات درونبرنامهای آغاز کنید. بازار رابط پرداخت برای کاربر را در روش پرداخت فراهم میکند، پس برنامهٔ شما به طور مستقیم تراکنش پرداخت را مدیریت نمیکند. وقتی محصولی خریداری شد، کافهبازار مالکیت کاربر روی محصول مورد نظر را بررسی میکند و از خرید محصول دیگری با همان شناسه، تا زمانی که آن محصول مصرف نشده، جلوگیری میکند. شما میتوانید مصرف محصول را در برنامهتان کنترل کنید و به بازار امکان خرید مجدد محصول توسط این کاربر را اطلاع دهید. همچنین این امکان وجود دارد که فهرست خریدهای کاربر را از بازار پرس و جو کنید. به عنوان مثال، این کار زمانی مفید است که بخواهید خریدهای مصرفنشدهٔ کاربر را به او نشان دهید.
در برنامهتان میتوانید جزئیات محصول را از بازار دریافت کنید. برای این کار نخست Bundle
ی را که شامل ArrayListی از رشتههای شناسهٔ محصولات با کلید "ITEM_ID_LIST"
است بسازید، که هر رشتهٔ شناسهٔ محصول برای یک محصولِ قابل خرید است.
ArrayList skuList = new ArrayList(); skuList.add("premiumUpgrade"); skuList.add("gas"); Bundle querySkus = new Bundle(); querySkus.putStringArrayList(“ITEM_ID_LIST”, skuList);
برای بازیابی این اطلاعات از بازار، متد getSkuDetails
را فراخوانی کنید و به این متد، ورودیهای نسخهٔ API
پرداخت درونبرنامهای ("3")، نام بستهٔ برنامهٔ خود، نوع خرید("inapp") و Bundle
ی که ساختید را بدهید.
Bundle skuDetails = mService.getSkuDetails(3, getPackageName(), "inapp", querySkus);
اگر درخواست موفقیتآمیز بود، خروجی Bundle
کد پاسخ BILLING_RESPONSE_RESULT_OK
(0) را خواهد داشت.
اخطار: متد getSkuDetails
را در Thread اصلی (Thread رابط کاربری) برنامهتان فراخوانی نکنید. فراخوانی این متد باعث ایجاد درخواستی شبکهای میشود که نباید در Thread اصلی (Thread رابط کاربری) برنامهٔ شما انجام شود و آن را بلوکه کند. در عوض یک Thread جداگانه بسازید و متد getSkuDetail
را از درون آن فراخوانی کنید.
برای دیدن تمامی کدهای پاسخ بازار API Reference را مشاهده کنید.
نتایج پرسوجو در یک رشته با کلید DETAILS_LIST
و اطلاعات خرید در یک رشته با
فرمت JSON ذخیره شده است. برای دیدن اطلاعات جزئیات محصول که پاسخ داده میشود، API Reference را مشاهده کنید.
int response = skuDetails.getInt("RESPONSE_CODE"); if (response == 0) { ArrayList responseList = skuDetails.getStringArrayList("DETAILS_LIST"); for (String thisResponse : responseList) { JSONObject object = new JSONObject(thisResponse); String sku = object.getString("productId"); String price = object.getString("price"); if (sku.equals("premiumUpgrade")) mPremiumUpgradePrice = price; else if (sku.equals("gas")) mGasPrice = price; } }
برای شروع درخواست خرید، متد getBuyIntent
را از سرویس پرداخت
درونبرنامهای فراخوانی کنید. به این متد ورودیهای نسخهٔ API پرداخت درونبرنامهای ("3")، نام بسته برنامهتان، شناسهٔ محصول قابل خریداری، نوع خرید(inapp”") و رشتهٔ developerPayload
را بدهید. رشتهٔ developerPayload
برای مشخص کردن هرگونه آرگومان اضافی که از بازار میخواهید همراه اطلاعات خرید برای شما فرستاده شود، استفاده میشود.
Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(), sku, "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
اگر درخواست موفقیت آمیز بود، Bundle
برگشتی کد پاسخ
BILLING_RESPONSE_RESULT_OK
و PendingIntent
ی که برای شروع جریان خرید میتوانید از آن استفاده کنید را به همراه دارد. برای دیدن تمامی کدهای پاسخ که ممکن از بازار فرستاده شود API Reference را مشاهده کنید.
سپس یک PendingIntent
از پاسخ Bundle
با کلید BUY_INTENT
استخراج کنید.
PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
برای تکمیل تراکنش خرید، متد startIntentSenderForResult
را فراخوانی کنید و از
PendingIntent
ی که خودتان ساختهاید، استفاده کنید. در این مثال شما از مقدار دلخواه 1001
برای کد درخواست استفاده میکنید.
startIntentSenderForResult(pendingIntent.getIntentSender(), 1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));
بازار پاسخ آن PendingIntent
را به متد onActivityResult
برنامهٔ شما میفرستد.
متد onActivityResult
با کد نتیجه Activity.RESULT_OK
یا Activity.RESULT_CANCELED
فراخوانی خواهد شد. به همراه این کد، Intent
ی خواهد بود که نقش حوالهٔ دیجیتالی خرید شما را خواهد داشت. این حواله در فرمت JSON است و با کلید INAPP_PURCHASE_DATA
در پاسخ Intent
قرار دارد. به عنوان مثال:
'{ "orderId":"rojeslcdyyiapnqcynkjyyjh", "packageName":"com.example.app", "productId":"exampleSku", "purchaseTime":1345678900000, "purchaseState":0, "developerPayload":"bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ", "purchaseToken":"rojeslcdyyiapnqcynkjyyjh" }'
برای دیدن فهرست کامل کلیدهای موجود در پاسخ برگردانده شده API Reference را مشاهده کنید.
در ادامهٔ مثال قبل، شما کد پاسخ ، دادهٔ خرید و امضای
پاسخ Intent
را دریافت میکنید.
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == 1001) { int responseCode = data.getIntExtra("RESPONSE_CODE", 0); String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA"); String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE"); if (resultCode == RESULT_OK) { try { JSONObject jo = new JSONObject(purchaseData); String sku = jo.getString("productId"); alert("You have bought the " + sku + ". Excellent choice, adventurer!"); } catch (JSONException e) { alert("Failed to parse purchase data."); e.printStackTrace(); } } } }پیشنهاد امنیتی: وقتی درخواست خرید را میفرستید، یک رشتهٔ token که به طور مشخص این درخواست خرید را بشناسد و شامل این token در
developerPayload
باشد،
ایجاد کنید. شما میتوانید از رشتههایی که به طور تصادفی ساخته میشوند به عنوان token استفاده کنید. وقتی پاسخ درخواست خرید را از بازار دریافت کردید، حتما امضای دادههای برگشتی، orderId
و رشته developerPayload
را بررسی کنید. برای امنیت بیشتر، این کار را در سرور امن خود نیز انجام دهید.
مطمئن شوید که مقدارorderId
منحصر به فرد است و شما قبلا پردازش نکردهاید و رشتهٔ developerPayload
با tokenی که شما قبلا با درخواست خرید فرستاده بودید، مطابقت دارد. برخی اوقات، ممکن است بعد از اینکه کاربر را به سیستم پرداخت درونبرنامهای منتقل نمودید، Activity
برنامهٔ شما به صورت خودکار توسط اندروید بسته شود. با اینکه پس از اتمام کار کاربر با سیستم پرداخت، اندروید به صورت خودکار دوباره Activity
برنامهٔ شما را میسازد، ولی تغییراتی که شما در Activity
مورد نظر دادهاید از بین خواهد رفت. لذا این اتفاق باعث میشود که کاربر خرید را انجام دهد ولی برنامهٔ شما نتواند آنرا ثبت کند. این اتفاق روی دستگاههای ضعیف اندرویدی به کرات و در گوشیهای قوی هم گاهی اتفاق میافتد.
برای جلوگیری از این کار، متد onSaveInstanceState
را برای Activity
ای که کاربر را به سیستم پرداخت میفرستد پیاده کنید و هر چیزی که میخواهید پس از بستهشدن Activity
باز یابید، در Bundleای که به عنوان آرگومان به متد گفته شده داده شده است، ست کنید. پس از بسته شدن Activity
، و باز شدن مجدد آن توسط اندروید، همین Bundle
ای که در متد onSaveInstanceState
تغییرش دادید، به عنوان آرگومان متد onCreate
در Activity
گفته شده داده خواهد شد، که میتوانید اطلاعات ذخیرهشدهتان را بازیابی کنید. برای اطلاعات بیشتر به
اینجا سر بزنید.
برای بازیابی اطلاعات خرید کاربر از برنامهتان، متد getPurchases
را فراخوانی کنید. به عنوان ورودی، شماره نسخهٔ API درون برنامهای ("3")، نام بسته برنامهتان و نوع خرید ("inapp") را بدهید.
Bundle ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null);
اگر درخواست موفقیت آمیز باشد، Bundle
برگشتی کد پاسخ 0
را خواهد داشت.
پاسخ Bundle
همچنین شامل لیستی از شناسهٔ محصولات، لیستی از جزئیات سفارش
هر خرید و امضای هر خرید است.
برای افزایش کارایی، سرویس پرداخت درون برنامهای، زمانی که برای اولین بار getPurchases
فراخوانیمیشود، فقط تا ۱۰۰ محصول را که متعلق به کاربر است باز میگرداند. اگر کاربر تعداد زیادی محصول خریداری شده داشته باشد، بازار رشتهٔ token که به کلید INAPP_CONTINUATION_TOKEN
، منطبق شده است را در پاسخ
Bundle
قرار میدهد تا نشان دهد محصولات بیشتری میتوانند بازیابی شوند.
سپس برنامه شما getPurchases
را فراخوانی میکند و این token را به عنوان آرگومان به آن میدهد.
بازار برگرداندن یک token دنبالهدار در Bundle
پاسخ را تا زمانی که تمام محصولاتی که متعلق به کاربر است به برنامه شما فرستاده شود، ادامه میدهد.
برای اطلاعات بیشتر در مورد دادههایی که توسط getPurchases
بازگردانده میشود، API Reference را مشاهده کنید. مثال زیر نشان میدهد که چگونه شما میتوانید این داده را دریافت کنید.
int response = ownedItems.getInt("RESPONSE_CODE"); if (response == 0) { ArrayList ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST"); ArrayList purchaseDataList = ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST"); ArrayList signatureList = ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE"); String continuationToken = ownedItems.getString("INAPP_CONTINUATION_TOKEN"); for (int i = 0; i < purchaseDataList.size(); ++i) { String purchaseData = purchaseDataList.get(i); String signature = signatureList.get(i); String sku = ownedSkus.get(i); // do something with this purchase information // e.g. display the updated list of products owned by user } // if continuationToken != null, call getPurchases again // and pass in the token to retrieve more items }
وقتی محصولی خریداری شد، به عنوان «دارایی تحت تملک» آن کاربر تلقی شده و نمیتواند دوباره توسط آن کاربر خریداری شود. شما باید درخواست مصرف محصول را برای آنکه بازار امکان خریداری مجدد آن را فراهم کند، بفرستید. چگونگی اجرای مکانیزم مصرف در برنامهتان بستگی به خودتان دارد. معمولا، شما محصولات موقتی و مصرفی (برای مثال سکه یا ابزار درون بازی) میفروشید که فروش چندینبارهٔ آن معنیدار است. برای محصولاتی که تنها یکبار فروخته میشوند و اثری دائمی دارند (مانند ارتقا دادن به نسخه کامل برنامه) مصرف را صدا نزنید.
برای ذخیره مصرف خرید، متد consumePurchase
را فراخوانی کنید و مقدار رشتهٔ purchaseToken
را به آن بدهید که خریدی را مشخص میکند که بنا بر مصرف آن است. در نتیجه خرید موفق، purchaseToken
قسمتی از دادهای است که در رشته
INAPP_PURCHASE_DATA
توسط بازار برگردانده میشود. در این مثال شما مصرف محصولی را که با purchaseToken
در متغیر token
مشخص میشود، ذخیره میکنید.
int response = mService.consumePurchase(3, getPackageName(), token);
اخطار: متد consumePurchase
را در thread اصلی (thread رابط کاربری) فراخوانی نکنید. فراخوانی کردن این متد باعث ایجاد درخواستی شبکهای میشود که thread اصلی شما (thread رابط کاربری) را بلوکه میکند. به جای آن یک thread جداگانه بسازید و متد consumePurchase
را از درون آن فراخوانی کنید.
مسئولیت شما تأمین کالای خریداری شده در درون برنامه است.. به عنوان مثال، اگر کاربر سکهٔ درون بازی را بخرد، شما باید فهرست اموال بازیکن را با مقدار سکهای که خریده است، به روز کنید.
پیشنهاد امنیتی: باید درخواست مصرف را قبل از فراهم کردن کالا، به بازار بفرستید. مطمئن شوید قبل از اینکه محصول را فراهم کنید، پاسخ مصرف تأییدشده از بازار دریافت کردهاید.
روند پیادهسازی اشتراک مانند روند پیادهسازی خرید محصول است با این تفاوت که نوع محصول باید برابر با subs
باشد. پاسخ و نتیجهٔ خرید دقیقا همانند خرید محصولات درونبرنامهای٬ به متد onActivityResult
شما ارسال میشود.
Bundle bundle = mService.getBuyIntent(3, "com.example.myapp", MY_SKU, "subs", developerPayload); PendingIntent pendingIntent = bundle.getParcelable(RESPONSE_BUY_INTENT); if (bundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK) { // Result will be delivered through onActivityResult(). startIntentSenderForResult(pendingIntent, RC_BUY, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0)); }
برای بازیابی اشتراکهای فعال٬ بار دیگر از متد getPurchases
استفاده کنید با این تفاوت که پارامتر مربوط به نوع محصول را برابر با subs
قرار دهید.
Bundle activeSubs = mService.getPurchases(3, "com.example.myapp", "subs", continueToken);
این فراخوانی یک Bundle
به شما باز میگرداند که شامل تمامی اشتراکهای فعال کاربر است. به محض منقضی شدن اشتراک (در صورت انصراف و یا عدم موجودی کاربر) دیگر در این Bundle
پدیدار نمیشود.
برای کسب اطمینان از بابت عدم دستکاری حوالهٔ خرید داده شده به برنامهتان، بازار رشتهٔ JSON ارسالی حاوی جزئیات خرید را امضا میکند. بازار از رمزنگاری نامتقارن برای ساختن این امضا استفاده میکند. به این ترتیب که با استفاده از کلید خصوصی تولید شده در سرور پرداخت، رشتهٔ مربوطه امضا شده است و این امضا توسط کلید عمومیای که در بخش «برنامههای فروشنده» در پنل پرداخت در اختیار شما قرار گرفته، قابل بررسی است.
شما میتوانید بررسی امضای گفته شده را بر روی برنامه خود انجام دهید، آنطور که در پروژهٔ TrivalDrive انجام شده است. ولی چنانچه برنامهتان برای ارائهٔ امکاناتش به سروری امن اتصال پیدا میکند، پیشنهاد ما انجام این بررسی بر روی آن سرور امن است.
برای کسب اطلاعات بیشتر درباره طراحیهای امنیتی به بخش ملاحظات امنیتی مراجعه کنید.