مدیریت پرداخت درون برنامه ایی و نظرات کاربران در مارکت های مختلف
با سلام
یکی از مشکلاتی که ممکنه باهاش مواجه بشید ، این هست که برنامه خودتون رو با وجود قابلیت پرداخت درون برنامه ایی بخواید توی مارکت های مختلف منتشر کنید که با وجود از طرف مارکت ها فقط میتونید دسترسی پرداخت همون مارکت رو استفاده کنید و ایجاد تغییرات مختلف ممکنه براتون دردسر ساز باشه
توی این آموزش چگونگی مدیریت پرداخت درون برنامه ایی برای مارکت های مختلف رو مرور میکنیم و همچنین مدیریت ارسال نظر به برنامه روی مارکت های محتلف و همچنین نمایش پیغام دانلود برای زمانی که شما از سمت سرور میخواید برنامه آپدیت بشه و برنامه باید برای دانلود به همون مارکتی کاربر رو هدایت کنه که کاربر برنامه رو از اونجا دریافت کرده ، در غیر اینصورت باتوجه به قوانین مارکت ها برنامه شما میتونه از انتشار خارج بشه
در ادامه به بررسی پرداخت درون برنامه ایی می پردازیم :
(ممکنه جمله بندی ناصحیح و غلط های املایی وجود داشته باشه ، و همچنین متدهایی که در ادامه گذاشته میشه میتونه تمیزتر و بهتر هم نوشته بشه ، من دیگه پست هارو ویرایش نمیکنه چون باعث میشه رنگ کدها سفید بشه و هی باید CtrL+A/X/V زد)
برای اضافه کردن قابلیت پرداخت درون برنامه به پروژه خودتون برای هر مارکتی در سرتاسر دنیا از همون مثال معروف TrivialDrive استفاده میشه که در نهایت روش پیاده سازی یک شکل هست و تنها تفاوت اونها در دو خط خلاصه میشه که این دو خط مشخص میکنه پروژه شما به کدوم سرویس و چه پکیجی باید صدا زده بشه تا پرداخت توسط اون سرویس که مربوطه به هر مارکتی میتونه باشه انجام بشه
این دو خط چیزی نیست جز خطا های زیر در کلاس IabHelper که در بین کلاس های پکیج utils قرار داره :
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
خب تا همینجا میشه گفت تمام مسئله روشن شد و تمام ، ولی ادامه میدیم
در ادامه ما کلاس IabHelper رو بصورت زیر دو پارامتر بهش اضاف میکنیم
String myPackageName;
String myAction;
و خطوط بالارو به هم بصورت زیر ویرایش میکنیم (ممکنه برای شما مقادیر موجود در پارامتر تفاوت داشته باشه و از نسخه های مختلف مارکت ها استفاده کرده باشید ) :
Intent serviceIntent = new Intent(myAction);
serviceIntent.setPackage(myPackageName);
متد زیر رو هم از کلاس IabHelper پیدا کنید
public IabHelper(Context ctx, String base64PublicKey) {
mContext = ctx.getApplicationContext();
mSignatureBase64 = base64PublicKey;
logDebug("IAB helper created.");
}
و به شکل زر تغییر بدید
public IabHelper(Context ctx, String base64PublicKey, String pPackageName, String pAction) {
myPackageName = pPackageName;
myAction = pAction;
mContext = ctx.getApplicationContext();
mSignatureBase64 = base64PublicKey;
logDebug("IAB helper created.");
}
نیاز داریم دسترسی برای مارکت های مختلف رو استفاده کنیم :
<!--
<uses-permission android:name="ir.mservices.market.BILLING" />
<uses-permission android:name="com.hrm.android.market.permission.PAY_THROUGH_MARKET" />
<uses-permission android:name="com.farsitel.bazaar.permission.PAY_THROUGH_BAZAAR" />
<uses-permission android:name="ir.tgbs.iranapps.permission.BILLING"/>
<uses-permission android:name="net.jhoobin.InAppBilling" />
-->
که به ترتیب برای مارکت های زیر هست :
- مایکت
- اول مارکت
- کافه بازار
- ایران اپس
- مارکت های پارس هاب و چارخونه (مشترک تحت نظر ژوبین)
در مورد دسترسی آخر که برای پارس هاب و چارخونه هست باید بگم که این دو مارکت اصلا برای پرداخت درون برنامه ایی نیازی به دسترسی ندارند و توی مستندات اصلا به این مورد اشاره ایی نشه و صحبت از هیچ دسترسی مختص به این مارکت ها برای خرید درون برنامه ایی وجود نداره ، ما دسترسی آخر رو از خودمون در آوردیم و همینطوری فقط برای سهولت کار اون رو نوشتیم و اصلا همچین دسترسی هیچ جایی بغیر از همین تاپیک بهش اشاره نشده ، به همین ترتیب شما میتونید از هر مارکتی که استفاده میکنید پرمیژن اون رو قرار بدید و یا خودتون براش یه پرمیژن بنویسید
در ادامه نیاز داریم که متوجه بشیم چه دسترسی در برنامه قرار داده شده و بر اساس اون پرداخت های درون برنامه ایی ، نظرات کاربران به برنامه و همچنین آدرس صفحه برنامه روی مارکت رو مدیریت کنیم ، که از متد زیر استفاده میکنیم
private int listIABpermission() {
try {
PackageManager packageManager = G.context.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(G.pInfo.packageName, PackageManager.GET_PERMISSIONS);
String[] permissions = packageInfo.requestedPermissions;
for (String per: permissions) {
if (per.equals("com.farsitel.bazaar.permission.PAY_THROUGH_BAZAAR")) {
return 1;
} else if (per.equals("ir.mservices.market.BILLING")) {
return 2;
} else if (per.equals("ir.tgbs.iranapps.permission.BILLING")) {
return 3;
} else if (per.equals("net.jhoobin.InAppBilling")) {
return 4;
} else if (per.equals("com.hrm.android.market.permission.PAY_THROUGH_MARKET")) {
return 5;
}
}
return 0;
}
catch (Exception e) {
e.printStackTrace();
return 0;
}
}
خروجی متد بالا رو در کلاس Application خودمون در یک متغیر از پیش تعریف شده قرار میدیم :
public static int iabPermissionId = 0;
@Override
public void onCreate() {
super.onCreate();
iabPermissionId = listIABpermission();
}
با توجه با داشتن مقدار iabPermissionId میتونیم در تمام قسمت های برنامه مدیریت خودمون رو بر اساس این متغیر انجام بدیم
قبل از اینکه به کمک iabPermissionId پرداخت های درون برنامه رو مدیریت کنیم ، امکان ارسال نظر و هدایت کاربر به برنامه در مارکت میپردازیم و در نهایت پرداخت درون برنامه ایی رو توضیح میدیم
برای ارسال نظر توسط کاربر کلاسی رو به مانند زیر ایجاد کنید
public class MarketsComment {
public static void comment() {
switch (G.iabPermissionId) {
case 1:
comment("com.farsitel.bazaar", "bazaar://details?id=", "https://cafebazaar.ir/app/");
break;
case 2:
comment("ir.mservices.market", "myket://comment?id=", "https://myket.ir/app/");
break;
case 3:
comment("ir.tgbs.android.iranapp", "iranapps://app/", "http://iranapps.ir/app/");
break;
case 4:
comment("net.jhoobin.jhub", "parshub://search?q=", "http://www.parshub.com/push/GAME/930503884&"); //jhoobin
break;
default:
G.toast("سیستم نظر دهی برای این مارکت وجود ندارد !", true);
break;
}
}
private static void comment(String pack, String marketAPI, String marketURL) {
G.toast(" نظرت راجب برنامه چیه؟ ", false);
try {
Intent intent = new Intent(Intent.ACTION_EDIT);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setPackage(pack);
intent.setData(Uri.parse(marketAPI + G.pInfo.packageName));
G.context.startActivity(intent);
}
catch (Exception e) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setPackage(null);
intent.setData(Uri.parse(marketURL + G.pInfo.packageName));
G.context.startActivity(intent);
}
}
}
و برای نظر خواهی از کاربر کافیه از هرجای برنامه خط زیر رو استفاده کنید
MarketsComment.comment();
در مورد هدایت کاربر به صفحه برنامه هم کافیه متد زیر رو استفاده کنید
public void openLink() {
String appUrl = "";
String pakage = "";
switch (G.iabPermissionId) {
case 1:
appUrl = "https://cafebazaar.ir/app/";
pakage = "com.farsitel.bazaar";
break;
case 2:
appUrl = "myket://comment?id=";
pakage = "ir.mservices.market";
break;
case 3:
appUrl = "ir.tgbs.android.iranapp";
pakage = "iranapps://app/";
break;
case 4:
appUrl = "jhoobin://search?q=";
pakage = null; //"net.jhoobin.jhub";
break;
default:
appUrl = "https://cafebazaar.ir/app/";
pakage = "com.farsitel.bazaar";
break;
}
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(appUrl + G.pInfo.packageName));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setPackage(pakage);
try {
getApplicationContext().startActivity(intent);
}
catch (ActivityNotFoundException ex) {
intent.setPackage(null);
getApplicationContext().startActivity(intent);
}
}
که البته این دو متد تفاوت زیادی با هم ندارند ولی در مورد case 4 میتونه جالب باشه که توی متد دوم برای هر دو مارکت پارس هاب و چارخونه کار میکنه اما توی روش اول case 4 فقط برای پارس هاب هست
در ادامه میپردازیم به خود پرداخت درون برنامه ایی :
از قبل ما کلاس IabHelper که در utils قرار داره رو ویرایش و آماده کردیم و تنها باید فرآیند رو به شکل صحیح انجام بدیم برای انجام پرداخت درون برنامه ایی از آموزش های محمدحسین کمی میگیریم و با کمی تغییرات جزئی استفاده میکنیم ، پس لازمه کلاسی به صورت زیر ساخته بشه
public class IABHelper {
public static IabHelper helper;
public static final String TAG = "IAB";
public static boolean isHelperSetup = false;
public static boolean moreCare = true;
public static String RSA = "";
public static void init(Context context) {
if (helper == null) {
try {
switch (G.iabPermissionId) {
case 1:
RSA = "MIHNM ...";
helper = new IabHelper(context, RSA, "com.farsitel.bazaar", "ir.cafebazaar.pardakht.InAppBillingService.BIND");
break;
case 2:
RSA = "MIGfM ...";
helper = new IabHelper(context, RSA, "ir.mservices.market", "ir.mservices.market.InAppBillingService.BIND");
break;
case 3:
RSA = "MIGfM ...";
helper = new IabHelper(context, RSA, "ir.tgbs.android.iranapp", "ir.tgbs.iranapps.billing.InAppBillingService.BIND");
break;
case 4:
RSA = "MIGfM ...";
helper = new IabHelper(context, RSA, null, "net.jhoobin.jhub.InAppBillingService.BIND");
break;
case 5:
RSA = "MIIBI ...";
helper = new IabHelper(context, RSA, "com.hrm.android.market", "com.hrm.android.market.billing.InAppBillingService.BIND");
break;
}
helper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
@Override
public void onIabSetupFinished(IabResult result) {
if ( !result.isSuccess()) {
Log.d(TAG, "Problem setting up In-app Billing: " + result);
} else {
Log.d(TAG, "setting up In-app Billing: " + result);
isHelperSetup = true;
}
}
});
}
catch (Exception e) {
e.printStackTrace();
}
}
}
public static void isUserPermium(final Context context, final IabHelper.QueryInventoryFinishedListener onInventoryFinishedListener) {
new Thread(new Runnable() {
@Override
public void run() {
boolean isListenerCall = false;
do {
if (helper == null) {
init(context);
}
if (helper != null && isHelperSetup) {
if (onInventoryFinishedListener != null) {
G.HANDLER.post(new Runnable() {
@Override
public void run() {
if (moreCare) {
moreCare = false;
Log.i("IAB", "IAB_QUERY");
helper.queryInventoryAsync(onInventoryFinishedListener);
}
}
});
isListenerCall = true;
}
}
try {
Thread.sleep(500);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
while (isHelperSetup && !isListenerCall);
}
}).start();
}
}
نکته ایی که در مورد کد بالا وجود داره این هست که ما در اینجا داریم مشخص میکنیم پرداخت درون برنامه ایی توسط کدوم مارکت انجام بشه که کد اون به شکل زیر هست :
switch (G.iabPermissionId) {
case 1:
RSA = "MIHNM ...";
helper = new IabHelper(context, RSA, "com.farsitel.bazaar", "ir.cafebazaar.pardakht.InAppBillingService.BIND");
break;
case 2:
RSA = "MIGfM ...";
helper = new IabHelper(context, RSA, "ir.mservices.market", "ir.mservices.market.InAppBillingService.BIND");
break;
case 3:
RSA = "MIGfM ...";
helper = new IabHelper(context, RSA, "ir.tgbs.android.iranapp", "ir.tgbs.iranapps.billing.InAppBillingService.BIND");
break;
case 4:
RSA = "MIGfM ...";
helper = new IabHelper(context, RSA, null, "net.jhoobin.jhub.InAppBillingService.BIND");
break;
case 5:
RSA = "MIIBI ...";
helper = new IabHelper(context, RSA, "com.hrm.android.market", "com.hrm.android.market.billing.InAppBillingService.BIND");
break;
}
در مورد case 4 نیازی به ارسال packageName نیست و برنامه شما بصورت پیشفرض روی هر دو مارکت پارس هاب و چارخونه قابلیت پرداخت درون برنامه ایی خواهد داشت
حالا باید فرآیند خرید درون برنامه ایی رو انجام بدیم :
توی هر اکتیویتی که میخواید پرداخت اونجا انجام بشه مقادیر زیر رو قرار بدید
public String SKU_PREMIUM = "coin_0";
public boolean mIsPremium = false;
public final int RC_REQUEST = 10101;
public IabHelper.OnIabPurchaseFinishedListener onPurchaseFinishedListener;
public IabHelper.OnConsumeFinishedListener mConsumeFinishedListener;
coin_0 برای من بصورت پیشفرض برای تست تعریف شده بود ، متد زیر رو قرار بدید
private void processIAB() {
final IabHelper.QueryInventoryFinishedListener onInventoryFinishedListener = new IabHelper.QueryInventoryFinishedListener() {
@Override
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Log.d(IABHelper.TAG, "Query inventory finished.");
if (result.isFailure()) {
Log.d(IABHelper.TAG, "Failed to query inventory: " + result);
if (result.getResponse() == -1003) {
G.toast("لامصب اختلاس میکنی !", true);
}
return;
} else {
Log.d(IABHelper.TAG, "Query inventory was successful.");
mIsPremium = inventory.hasPurchase(SKU_PREMIUM);
if (mIsPremium) {
IABHelper.helper.consumeAsync(inventory.getPurchase(SKU_PREMIUM), mConsumeFinishedListener);
}
Log.d(IABHelper.TAG, "User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM"));
}
Log.d(IABHelper.TAG, "Initial inventory query finished; enabling main UI.");
}
};
onPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
@Override
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
if (result.isFailure()) {
Log.d(IABHelper.TAG, "Error purchasing: " + result);
if (result.getResponse() == -1003) {
G.toast("بابا از خر شیطون بیا پایین", true);
}
if (result.getResponse() == 7) {
IABHelper.helper.queryInventoryAsync(onInventoryFinishedListener);
G.toast("بررسی صحت پرداخت", false);
}
return;
} else if (purchase.getSku().equals(SKU_PREMIUM)) {
IABHelper.helper.queryInventoryAsync(onInventoryFinishedListener);
G.toast("بررسی صحت پرداخت", false);
}
}
};
mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() {
@Override
public void onConsumeFinished(Purchase purchase, IabResult result) {
if (result.isSuccess()) {
G.toast("افزایش سکه با موفقیت انجام شد", true);
manageConsumeCoin(purchase.getSku());
} else {
G.toast("مشکلی در روند کار پیش آمده", true);
}
}
};
IABHelper.isUserPermium(BaseActivityMenu.this, onInventoryFinishedListener);
}
متد بالا در OnCreate و یا قبل از اینکه کاربر رو برای خرید هدایت کنید باید صدا زده باشه (قطعه کد اول در زیر) تا پرداخت درون برنامه ایی آماده سازی بشه ، بعد از آماده شدن پرداخت درون برنامه و مشخص شدن SKU میتونید متد زیر رو برای هدایت کاربر به پرداخت استفاده کنید ، به شکل زیر قطعه کد دوم
if (G.iabPermissionId > 0) {
processIAB();
}
private void iab() {
try {
if (IABHelper.isHelperSetup) {
Log.i("IAB", "SKU : " + SKU_PREMIUM);
IABHelper.helper.launchPurchaseFlow(BaseActivityMenu.this, SKU_PREMIUM, RC_REQUEST, onPurchaseFinishedListener);
}
}
catch (Exception e) {
e.printStackTrace();
}
}
بعد از انجام شدن یا نشدن پرداخت توسط کاربر Listener های موجود در متد processIAB صدا زده میشه اگر پرداخت موفق باشه شما پرداخت درون برنامه ایی بصورت عادی داشتید اما اگر میخواید پرداخت درون برنامه ایی مصرفی داشته باشید و کاربر بارها بتونه در قسمت های مختلف برنامه پرداخت های متفاوتی رو انجام بده باید خط زیر رو اجرا کنید تا اون SKU که پرداخت شده مصرف بشه
IABHelper.helper.consumeAsync(inventory.getPurchase(SKU_PREMIUM), mConsumeFinishedListener);
بعد از مصرف شدن پرداخت لیستنر زیر صدا زده میشه و میتونید با توجه به SKU که مصرف شده متد manageConsumeCoin(purchase.getSku()) رو صدا بزنید و پروسه پرداخت درون برنامه مصرفی رو در اون متد انجام بدید که این مورد دیگه بستگی به خودتون داره
mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() {
@Override
public void onConsumeFinished(Purchase purchase, IabResult result) {
if (result.isSuccess()) {
G.toast("افزایش سکه با موفقیت انجام شد", true);
manageConsumeCoin(purchase.getSku());
} else {
G.toast("مشکلی در روند کار پیش آمده", true);
}
}
};
این خطور رو در متد onActivityResult قرار بدید
try {
if (G.iabPermissionId > 0) {
if ( !IABHelper.helper.handleActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
} else {
Log.d(IABHelper.TAG, "onActivityResult handled by IABUtil.");
}
}
}
catch (Exception e) {
e.printStackTrace();
}
در مورد کدهای استفاده از 7 و -10003 میتونید به کلاس IabHelper برید و کدهارو ببینید :
// Billing response codes
public static final int BILLING_RESPONSE_RESULT_OK = 0;
public static final int BILLING_RESPONSE_RESULT_USER_CANCELED = 1;
public static final int BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3;
public static final int BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE = 4;
public static final int BILLING_RESPONSE_RESULT_DEVELOPER_ERROR = 5;
public static final int BILLING_RESPONSE_RESULT_ERROR = 6;
public static final int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7;
public static final int BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8;
// IAB Helper error codes
public static final int IABHELPER_ERROR_BASE = -1000;
public static final int IABHELPER_REMOTE_EXCEPTION = -1001;
public static final int IABHELPER_BAD_RESPONSE = -1002;
public static final int IABHELPER_VERIFICATION_FAILED = -1003;
public static final int IABHELPER_SEND_INTENT_FAILED = -1004;
public static final int IABHELPER_USER_CANCELLED = -1005;
public static final int IABHELPER_UNKNOWN_PURCHASE_RESPONSE = -1006;
public static final int IABHELPER_MISSING_TOKEN = -1007;
public static final int IABHELPER_UNKNOWN_ERROR = -1008;
public static final int IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE = -1009;
public static final int IABHELPER_INVALID_CONSUMPTION = -1010;
این مورد هم فراموش کردم که بگم ، لازمه از نصب بودن مارکت ها روی دستگاه کاربر اطمینان حاصل کنید ، که بصورت زیر انجام میشه ، شما فقط باید کمی کد رو تغییر بدید و استفاده کنید ، زمانی استفاده لازم میشه دستور در کلاس IABHelper خط زیر صدا زده میشه و ما پکیج رو ارسال میکنیم و اگر اون پکیج نصب نباشه خطای ActivityNotFound میگیریم
helper = new IabHelper(context, RSA, "com.farsitel.bazaar", "ir.cafebazaar.pardakht.InAppBillingService.BIND");
public boolean isPackageInstalled(String PackageName) {
//com.farsitel.bazaar
PackageManager manager = getPackageManager();
boolean isAppInstalled = false;
try {
manager.getPackageInfo(PackageName, PackageManager.GET_ACTIVITIES);
isAppInstalled = true;
}
catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return isAppInstalled;
}
حالا اگر هر View دیالوگ یا هرچیزی برای هدایت کاربر به پرداخت درون برنامه ایی دارید ، بصورت زیر میتوید بگیذ نمایش داده بشه و یا Gone باشه و نمایش داده نشه
if (G.iabPermissionId > 0) {
//
}
در نهایت فقط کافیه دسترسی مارکت مورد نظر خودتون رو قرار بدید و از پروژه خروجی بگیرید و APK رو آپلود کنید نیاز به تغییرات دیگری ندارید :
<uses-permission android:name="ir.mservices.market.BILLING" />
<!--
<uses-permission android:name="com.hrm.android.market.permission.PAY_THROUGH_MARKET" />
<uses-permission android:name="com.farsitel.bazaar.permission.PAY_THROUGH_BAZAAR" />
<uses-permission android:name="ir.tgbs.iranapps.permission.BILLING"/>
<uses-permission android:name="net.jhoobin.InAppBilling" />
-->
در نهایت این رو بگم که اگر در پروژه هاتون از gradle استفاده میکنید ، و وقتش رو دارید این میتونه برای شما مفید باشه : لینک
دوباره از این مطلب خیلی خوبت تشکر می کنم خیلی به من کمک کرد
یک مشکلی با پارس هاب و چارخونه داشت من فایل اونا را دیدم اونا هم پکیج نیم ست می کنند الان این یک تکه را عوض کردم
if(myPackageName==null||myPackageName.length()<2){
List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServices(serviceIntent, 0);
serviceIntent.setPackage(resolveInfos.get(0).serviceInfo.packageName);//set package for all jhoobin markets
}else {
serviceIntent.setPackage(myPackageName);
}
کلاس IabHelper اصلاح شده که برای چارخونه و پارس هاب هم کار می کنه:
/* Copyright (c) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package util;
import java.util.ArrayList;
import java.util.List;
import org.json.JSONException;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.content.ServiceConnection;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import com.android.vending.billing.IInAppBillingService;
import com.anasoftco.mycar.global.G;
/**
* Provides convenience methods for in-app billing. You can create one instance of this
* class for your application and use it to process in-app billing operations.
* It provides synchronous (blocking) and asynchronous (non-blocking) methods for
* many common in-app billing operations, as well as automatic signature
* verification.
*
* After instantiating, you must perform setup in order to start using the object.
* To perform setup, call the {@link #startSetup} method and provide a listener;
* that listener will be notified when setup is complete, after which (and not before)
* you may call other methods.
*
* After setup is complete, you will typically want to request an inventory of owned
* items and subscriptions. See {@link #queryInventory}, {@link #queryInventoryAsync}
* and related methods.
*
* When you are done with this object, don't forget to call {@link #dispose}
* to ensure proper cleanup. This object holds a binding to the in-app billing
* service, which will leak unless you dispose of it correctly. If you created
* the object on an Activity's onCreate method, then the recommended
* place to dispose of it is the Activity's onDestroy method.
*
* A note about threading: When using this object from a background thread, you may
* call the blocking versions of methods; when using from a UI thread, call
* only the asynchronous versions and handle the results via callbacks.
* Also, notice that you can only call one asynchronous operation at a time;
* attempting to start a second asynchronous operation while the first one
* has not yet completed will result in an exception being thrown.
*
* @author Bruno Oliveira (Google)
*
*/
public class IabHelper {
// Is debug logging enabled?
boolean mDebugLog = false;
String mDebugTag = "IabHelper";
String myPackageName;
String myAction;
// Is setup done?
boolean mSetupDone = false;
// Has this object been disposed of? (If so, we should ignore callbacks, etc)
boolean mDisposed = false;
// Are subscriptions supported?
boolean mSubscriptionsSupported = false;
// Is an asynchronous operation in progress?
// (only one at a time can be in progress)
boolean mAsyncInProgress = false;
// (for logging/debugging)
// if mAsyncInProgress == true, what asynchronous operation is in progress?
String mAsyncOperation = "";
// Context we were passed during initialization
Context mContext;
// Connection to the service
IInAppBillingService mService;
ServiceConnection mServiceConn;
// The request code used to launch purchase flow
int mRequestCode;
// The item type of the current purchase flow
String mPurchasingItemType;
// Public key for verifying signature, in base64 encoding
String mSignatureBase64 = null;
// Billing response codes
public static final int BILLING_RESPONSE_RESULT_OK = 0;
public static final int BILLING_RESPONSE_RESULT_USER_CANCELED = 1;
public static final int BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3;
public static final int BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE = 4;
public static final int BILLING_RESPONSE_RESULT_DEVELOPER_ERROR = 5;
public static final int BILLING_RESPONSE_RESULT_ERROR = 6;
public static final int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7;
public static final int BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8;
// IAB Helper error codes
public static final int IABHELPER_ERROR_BASE = -1000;
public static final int IABHELPER_REMOTE_EXCEPTION = -1001;
public static final int IABHELPER_BAD_RESPONSE = -1002;
public static final int IABHELPER_VERIFICATION_FAILED = -1003;
public static final int IABHELPER_SEND_INTENT_FAILED = -1004;
public static final int IABHELPER_USER_CANCELLED = -1005;
public static final int IABHELPER_UNKNOWN_PURCHASE_RESPONSE = -1006;
public static final int IABHELPER_MISSING_TOKEN = -1007;
public static final int IABHELPER_UNKNOWN_ERROR = -1008;
public static final int IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE = -1009;
public static final int IABHELPER_INVALID_CONSUMPTION = -1010;
// Keys for the responses from InAppBillingService
public static final String RESPONSE_CODE = "RESPONSE_CODE";
public static final String RESPONSE_GET_SKU_DETAILS_LIST = "DETAILS_LIST";
public static final String RESPONSE_BUY_INTENT = "BUY_INTENT";
public static final String RESPONSE_INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA";
public static final String RESPONSE_INAPP_SIGNATURE = "INAPP_DATA_SIGNATURE";
public static final String RESPONSE_INAPP_ITEM_LIST = "INAPP_PURCHASE_ITEM_LIST";
public static final String RESPONSE_INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST";
public static final String RESPONSE_INAPP_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST";
public static final String INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN";
// Item types
public static final String ITEM_TYPE_INAPP = "inapp";
public static final String ITEM_TYPE_SUBS = "subs";
// some fields on the getSkuDetails response bundle
public static final String GET_SKU_DETAILS_ITEM_LIST = "ITEM_ID_LIST";
public static final String GET_SKU_DETAILS_ITEM_TYPE_LIST = "ITEM_TYPE_LIST";
/**
* Creates an instance. After creation, it will not yet be ready to use. You must perform
* setup by calling {@link #startSetup} and wait for setup to complete. This constructor does not
* block and is safe to call from a UI thread.
*
* @param ctx Your application or Activity context. Needed to bind to the in-app billing service.
* @param base64PublicKey Your application's public key, encoded in base64.
* This is used for verification of purchase signatures. You can find your app's base64-encoded
* public key in your application's page on Google Play Developer Console. Note that this
* is NOT your "developer public key".
*/
public IabHelper(Context ctx, String base64PublicKey, String pPackageName, String pAction) {
myPackageName = pPackageName;
myAction = pAction;
mContext = ctx.getApplicationContext();
mSignatureBase64 = base64PublicKey;
logDebug("IAB helper created.");
}
/**
* Enables or disable debug logging through LogCat.
*/
public void enableDebugLogging(boolean enable, String tag) {
checkNotDisposed();
mDebugLog = enable;
mDebugTag = tag;
}
public void enableDebugLogging(boolean enable) {
checkNotDisposed();
mDebugLog = enable;
}
/**
* Callback for setup process. This listener's {@link #onIabSetupFinished} method is called
* when the setup process is complete.
*/
public interface OnIabSetupFinishedListener {
/**
* Called to notify that setup is complete.
*
* @param result The result of the setup process.
*/
public void onIabSetupFinished(util.IabResult result);
}
/**
* Starts the setup process. This will start up the setup process asynchronously.
* You will be notified through the listener when the setup process is complete.
* This method is safe to call from a UI thread.
*
* @param listener The listener to notify when the setup process is complete.
*/
public void startSetup(final OnIabSetupFinishedListener listener) {
// If already set up, can't do it again.
checkNotDisposed();
if (mSetupDone) throw new IllegalStateException("IAB helper is already set up.");
// Connection to IAB service
logDebug("Starting in-app billing setup.");
mServiceConn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
logDebug("Billing service disconnected.");
mService = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (mDisposed) return;
logDebug("Billing service connected.");
mService = IInAppBillingService.Stub.asInterface(service);
String packageName = mContext.getPackageName();
try {
logDebug("Checking for in-app billing 3 support.");
// check for in-app billing v3 support
int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);
if (response != BILLING_RESPONSE_RESULT_OK) {
if (listener != null) listener.onIabSetupFinished(new util.IabResult(response,
"Error checking for billing v3 support."));
// if in-app purchases aren't supported, neither are subscriptions.
mSubscriptionsSupported = false;
return;
}
logDebug("In-app billing version 3 supported for " + packageName);
// check for v3 subscriptions support
response = mService.isBillingSupported(3, packageName, ITEM_TYPE_SUBS);
if (response == BILLING_RESPONSE_RESULT_OK) {
logDebug("Subscriptions AVAILABLE.");
mSubscriptionsSupported = true;
}
else {
logDebug("Subscriptions NOT AVAILABLE. Response: " + response);
}
mSetupDone = true;
}
catch (RemoteException e) {
if (listener != null) {
listener.onIabSetupFinished(new util.IabResult(IABHELPER_REMOTE_EXCEPTION,
"RemoteException while setting up in-app billing."));
}
e.printStackTrace();
return;
}
if (listener != null) {
listener.onIabSetupFinished(new util.IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));
}
}
};
Intent serviceIntent = new Intent(myAction);
if(myPackageName==null||myPackageName.length()<2){
List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServices(serviceIntent, 0);
serviceIntent.setPackage(resolveInfos.get(0).serviceInfo.packageName);//set package for all jhoobin markets
}else {
serviceIntent.setPackage(myPackageName);
}
try {
if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) {
// service available to handle that Intent
mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
}
else {
// no service available to handle that Intent
if (listener != null) {
listener.onIabSetupFinished(
new util.IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
"Billing service unavailable on device."));
}
}
}catch (Exception e){
Log.e(G.TAG,"labHelper Error line 277" + e);
}
}
/**
* Dispose of object, releasing resources. It's very important to call this
* method when you are done with this object. It will release any resources
* used by it such as service connections. Naturally, once the object is
* disposed of, it can't be used again.
*/
public void dispose() {
logDebug("Disposing.");
mSetupDone = false;
if (mServiceConn != null) {
logDebug("Unbinding from service.");
if (mContext != null) mContext.unbindService(mServiceConn);
}
mDisposed = true;
mContext = null;
mServiceConn = null;
mService = null;
mPurchaseListener = null;
}
private void checkNotDisposed() {
if (mDisposed) throw new IllegalStateException("IabHelper was disposed of, so it cannot be used.");
}
/** Returns whether subscriptions are supported. */
public boolean subscriptionsSupported() {
checkNotDisposed();
return mSubscriptionsSupported;
}
/**
* Callback that notifies when a purchase is finished.
*/
public interface OnIabPurchaseFinishedListener {
/**
* Called to notify that an in-app purchase finished. If the purchase was successful,
* then the sku parameter specifies which item was purchased. If the purchase failed,
* the sku and extraData parameters may or may not be null, depending on how far the purchase
* process went.
*
* @param result The result of the purchase.
* @param info The purchase information (null if purchase failed)
*/
public void onIabPurchaseFinished(util.IabResult result, Purchase info);
}
// The listener registered on launchPurchaseFlow, which we have to call back when
// the purchase finishes
OnIabPurchaseFinishedListener mPurchaseListener;
public void launchPurchaseFlow(Activity act, String sku, int requestCode, OnIabPurchaseFinishedListener listener) {
launchPurchaseFlow(act, sku, requestCode, listener, "");
}
public void launchPurchaseFlow(Activity act, String sku, int requestCode,OnIabPurchaseFinishedListener listener, String extraData) {
launchPurchaseFlow(act, sku, ITEM_TYPE_INAPP, requestCode, listener, extraData);
}
public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode,
OnIabPurchaseFinishedListener listener) {
launchSubscriptionPurchaseFlow(act, sku, requestCode, listener, "");
}
public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode,
OnIabPurchaseFinishedListener listener, String extraData) {
launchPurchaseFlow(act, sku, ITEM_TYPE_SUBS, requestCode, listener, extraData);
}
/**
* Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app purchase,
* which will involve bringing up the Google Play screen. The calling activity will be paused while
* the user interacts with Google Play, and the result will be delivered via the activity's
* {@link android.app.Activity#onActivityResult} method, at which point you must call
* this object's {@link #handleActivityResult} method to continue the purchase flow. This method
* MUST be called from the UI thread of the Activity.
*
* @param act The calling activity.
* @param sku The sku of the item to purchase.
* @param itemType indicates if it's a product or a subscription (ITEM_TYPE_INAPP or ITEM_TYPE_SUBS)
* @param requestCode A request code (to differentiate from other responses --
* as in {@link android.app.Activity#startActivityForResult}).
* @param listener The listener to notify when the purchase process finishes
* @param extraData Extra data (developer payload), which will be returned with the purchase data
* when the purchase completes. This extra data will be permanently bound to that purchase
* and will always be returned when the purchase is queried.
*/
public void launchPurchaseFlow(Activity act, String sku, String itemType, int requestCode, OnIabPurchaseFinishedListener listener, String extraData) {
checkNotDisposed();
checkSetupDone("launchPurchaseFlow");
flagStartAsync("launchPurchaseFlow");
util.IabResult result;
if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {
util.IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,
"Subscriptions are not available.");
flagEndAsync();
if (listener != null) listener.onIabPurchaseFinished(r, null);
return;
}
try {
logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);
Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData);
int response = getResponseCodeFromBundle(buyIntentBundle);
if (response != BILLING_RESPONSE_RESULT_OK) {
logError("Unable to buy item, Error response: " + getResponseDesc(response));
flagEndAsync();
result = new util.IabResult(response, "Unable to buy item");
if (listener != null) listener.onIabPurchaseFinished(result, null);
return;
}
PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
mRequestCode = requestCode;
mPurchaseListener = listener;
mPurchasingItemType = itemType;
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
requestCode, new Intent(),
Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0));
}
catch (SendIntentException e) {
logError("SendIntentException while launching purchase flow for sku " + sku);
e.printStackTrace();
flagEndAsync();
result = new util.IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");
if (listener != null) listener.onIabPurchaseFinished(result, null);
}
catch (RemoteException e) {
logError("RemoteException while launching purchase flow for sku " + sku);
e.printStackTrace();
flagEndAsync();
result = new util.IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");
if (listener != null) listener.onIabPurchaseFinished(result, null);
}
}
/**
* Handles an activity result that's part of the purchase flow in in-app billing. If you
* are calling {@link #launchPurchaseFlow}, then you must call this method from your
* Activity's {@link android.app.Activity@onActivityResult} method. This method
* MUST be called from the UI thread of the Activity.
*
* @param requestCode The requestCode as you received it.
* @param resultCode The resultCode as you received it.
* @param data The data (Intent) as you received it.
* @return Returns true if the result was related to a purchase flow and was handled;
* false if the result was not related to a purchase, in which case you should
* handle it normally.
*/
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
util.IabResult result;
if (requestCode != mRequestCode) return false;
checkNotDisposed();
checkSetupDone("handleActivityResult");
// end of async purchase operation that started on launchPurchaseFlow
flagEndAsync();
if (data == null) {
logError("Null data in IAB activity result.");
result = new util.IabResult(IABHELPER_BAD_RESPONSE, "Null data in IAB result");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
return true;
}
int responseCode = getResponseCodeFromIntent(data);
String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);
if (resultCode == Activity.RESULT_OK && responseCode == BILLING_RESPONSE_RESULT_OK) {
logDebug("Successful resultcode from purchase activity.");
logDebug("Purchase data: " + purchaseData);
logDebug("Data signature: " + dataSignature);
logDebug("Extras: " + data.getExtras());
logDebug("Expected item type: " + mPurchasingItemType);
if (purchaseData == null || dataSignature == null) {
logError("BUG: either purchaseData or dataSignature is null.");
logDebug("Extras: " + data.getExtras().toString());
result = new util.IabResult(IABHELPER_UNKNOWN_ERROR, "IAB returned null purchaseData or dataSignature");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
return true;
}
util.Purchase purchase = null;
try {
purchase = new util.Purchase(mPurchasingItemType, purchaseData, dataSignature);
String sku = purchase.getSku();
// Verify signature
if (!util.Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) {
logError("Purchase signature verification FAILED for sku " + sku);
result = new util.IabResult(IABHELPER_VERIFICATION_FAILED, "Signature verification failed for sku " + sku);
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, purchase);
return true;
}
logDebug("Purchase signature successfully verified.");
}
catch (JSONException e) {
logError("Failed to parse purchase data.");
e.printStackTrace();
result = new util.IabResult(IABHELPER_BAD_RESPONSE, "Failed to parse purchase data.");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
return true;
}
if (mPurchaseListener != null) {
mPurchaseListener.onIabPurchaseFinished(new util.IabResult(BILLING_RESPONSE_RESULT_OK, "Success"), purchase);
}
}
else if (resultCode == Activity.RESULT_OK) {
// result code was OK, but in-app billing response was not OK.
logDebug("Result code was OK but in-app billing response was not OK: " + getResponseDesc(responseCode));
if (mPurchaseListener != null) {
result = new util.IabResult(responseCode, "Problem purchashing item.");
mPurchaseListener.onIabPurchaseFinished(result, null);
}
}
else if (resultCode == Activity.RESULT_CANCELED) {
logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode));
result = new util.IabResult(IABHELPER_USER_CANCELLED, "User canceled.");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
}
else {
logError("Purchase failed. Result code: " + Integer.toString(resultCode)
+ ". Response: " + getResponseDesc(responseCode));
result = new util.IabResult(IABHELPER_UNKNOWN_PURCHASE_RESPONSE, "Unknown purchase response.");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
}
return true;
}
public util.Inventory queryInventory(boolean querySkuDetails, List<String> moreSkus) throws IabException {
return queryInventory(querySkuDetails, moreSkus, null);
}
/**
* Queries the inventory. This will query all owned items from the server, as well as
* information on additional skus, if specified. This method may block or take long to execute.
* Do not call from a UI thread. For that, use the non-blocking version {@link #refreshInventoryAsync}.
*
* @param querySkuDetails if true, SKU details (price, description, etc) will be queried as well
* as purchase information.
* @param moreItemSkus additional PRODUCT skus to query information on, regardless of ownership.
* Ignored if null or if querySkuDetails is false.
* @param moreSubsSkus additional SUBSCRIPTIONS skus to query information on, regardless of ownership.
* Ignored if null or if querySkuDetails is false.
* @throws IabException if a problem occurs while refreshing the inventory.
*/
public util.Inventory queryInventory(boolean querySkuDetails, List<String> moreItemSkus,
List<String> moreSubsSkus) throws IabException {
checkNotDisposed();
checkSetupDone("queryInventory");
try {
util.Inventory inv = new Inventory();
int r = queryPurchases(inv, ITEM_TYPE_INAPP);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying owned items).");
}
if (querySkuDetails) {
r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying prices of items).");
}
}
// if subscriptions are supported, then also query for subscriptions
if (mSubscriptionsSupported) {
r = queryPurchases(inv, ITEM_TYPE_SUBS);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying owned subscriptions).");
}
if (querySkuDetails) {
r = querySkuDetails(ITEM_TYPE_SUBS, inv, moreItemSkus);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying prices of subscriptions).");
}
}
}
return inv;
}
catch (RemoteException e) {
throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while refreshing inventory.", e);
}
catch (JSONException e) {
throw new IabException(IABHELPER_BAD_RESPONSE, "Error parsing JSON response while refreshing inventory.", e);
}
}
/**
* Listener that notifies when an inventory query operation completes.
*/
public interface QueryInventoryFinishedListener {
/**
* Called to notify that an inventory query operation completed.
*
* @param result The result of the operation.
* @param inv The inventory.
*/
public void onQueryInventoryFinished(util.IabResult result, Inventory inv);
}
/**
* Asynchronous wrapper for inventory query. This will perform an inventory
* query as described in {@link #queryInventory}, but will do so asynchronously
* and call back the specified listener upon completion. This method is safe to
* call from a UI thread.
*
* @param querySkuDetails as in {@link #queryInventory}
* @param moreSkus as in {@link #queryInventory}
* @param listener The listener to notify when the refresh operation completes.
*/
public void queryInventoryAsync(final boolean querySkuDetails,
final List<String> moreSkus,
final QueryInventoryFinishedListener listener) {
final Handler handler = new Handler();
checkNotDisposed();
checkSetupDone("queryInventory");
flagStartAsync("refresh inventory");
(new Thread(new Runnable() {
public void run() {
util.IabResult result = new IabResult(BILLING_RESPONSE_RESULT_OK, "Inventory refresh successful.");
util.Inventory inv = null;
try {
inv = queryInventory(querySkuDetails, moreSkus);
}
catch (IabException ex) {
result = ex.getResult();
}
flagEndAsync();
final util.IabResult result_f = result;
final util.Inventory inv_f = inv;
if (!mDisposed && listener != null) {
handler.post(new Runnable() {
public void run() {
listener.onQueryInventoryFinished(result_f, inv_f);
}
});
}
}
})).start();
}
public void queryInventoryAsync(QueryInventoryFinishedListener listener) {
queryInventoryAsync(true, null, listener);
}
public void queryInventoryAsync(boolean querySkuDetails, QueryInventoryFinishedListener listener) {
queryInventoryAsync(querySkuDetails, null, listener);
}
/**
* Consumes a given in-app product. Consuming can only be done on an item
* that's owned, and as a result of consumption, the user will no longer own it.
* This method may block or take long to return. Do not call from the UI thread.
* For that, see {@link #consumeAsync}.
*
* @param itemInfo The PurchaseInfo that represents the item to consume.
* @throws IabException if there is a problem during consumption.
*/
void consume(util.Purchase itemInfo) throws IabException {
checkNotDisposed();
checkSetupDone("consume");
if (!itemInfo.mItemType.equals(ITEM_TYPE_INAPP)) {
throw new IabException(IABHELPER_INVALID_CONSUMPTION,
"Items of type '" + itemInfo.mItemType + "' can't be consumed.");
}
try {
String token = itemInfo.getToken();
String sku = itemInfo.getSku();
if (token == null || token.equals("")) {
logError("Can't consume "+ sku + ". No token.");
throw new IabException(IABHELPER_MISSING_TOKEN, "PurchaseInfo is missing token for sku: "
+ sku + " " + itemInfo);
}
logDebug("Consuming sku: " + sku + ", token: " + token);
int response = mService.consumePurchase(3, mContext.getPackageName(), token);
if (response == BILLING_RESPONSE_RESULT_OK) {
logDebug("Successfully consumed sku: " + sku);
}
else {
logDebug("Error consuming consuming sku " + sku + ". " + getResponseDesc(response));
throw new IabException(response, "Error consuming sku " + sku);
}
}
catch (RemoteException e) {
throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while consuming. PurchaseInfo: " + itemInfo, e);
}
}
/**
* Callback that notifies when a consumption operation finishes.
*/
public interface OnConsumeFinishedListener {
/**
* Called to notify that a consumption has finished.
*
* @param purchase The purchase that was (or was to be) consumed.
* @param result The result of the consumption operation.
*/
public void onConsumeFinished(util.Purchase purchase, IabResult result);
}
/**
* Callback that notifies when a multi-item consumption operation finishes.
*/
public interface OnConsumeMultiFinishedListener {
/**
* Called to notify that a consumption of multiple items has finished.
*
* @param purchases The purchases that were (or were to be) consumed.
* @param results The results of each consumption operation, corresponding to each
* sku.
*/
public void onConsumeMultiFinished(List<util.Purchase> purchases, List<IabResult> results);
}
/**
* Asynchronous wrapper to item consumption. Works like {@link #consume}, but
* performs the consumption in the background and notifies completion through
* the provided listener. This method is safe to call from a UI thread.
*
* @param purchase The purchase to be consumed.
* @param listener The listener to notify when the consumption operation finishes.
*/
public void consumeAsync(util.Purchase purchase, OnConsumeFinishedListener listener) {
checkNotDisposed();
checkSetupDone("consume");
List<util.Purchase> purchases = new ArrayList<Purchase>();
purchases.add(purchase);
consumeAsyncInternal(purchases, listener, null);
}
/**
* Same as {@link consumeAsync}, but for multiple items at once.
* @param purchases The list of PurchaseInfo objects representing the purchases to consume.
* @param listener The listener to notify when the consumption operation finishes.
*/
public void consumeAsync(List<util.Purchase> purchases, OnConsumeMultiFinishedListener listener) {
checkNotDisposed();
checkSetupDone("consume");
consumeAsyncInternal(purchases, null, listener);
}
/**
* Returns a human-readable description for the given response code.
*
* @param code The response code
* @return A human-readable string explaining the result code.
* It also includes the result code numerically.
*/
public static String getResponseDesc(int code) {
String[] iab_msgs = ("0:OK/1:User Canceled/2:Unknown/" +
"3:Billing Unavailable/4:Item unavailable/" +
"5:Developer Error/6:Error/7:Item Already Owned/" +
"8:Item not owned").split("/");
String[] iabhelper_msgs = ("0:OK/-1001:Remote exception during initialization/" +
"-1002:Bad response received/" +
"-1003:Purchase signature verification failed/" +
"-1004:Send intent failed/" +
"-1005:User cancelled/" +
"-1006:Unknown purchase response/" +
"-1007:Missing token/" +
"-1008:Unknown error/" +
"-1009:Subscriptions not available/" +
"-1010:Invalid consumption attempt").split("/");
if (code <= IABHELPER_ERROR_BASE) {
int index = IABHELPER_ERROR_BASE - code;
if (index >= 0 && index < iabhelper_msgs.length) return iabhelper_msgs[index];
else return String.valueOf(code) + ":Unknown IAB Helper Error";
}
else if (code < 0 || code >= iab_msgs.length)
return String.valueOf(code) + ":Unknown";
else
return iab_msgs[code];
}
// Checks that setup was done; if not, throws an exception.
void checkSetupDone(String operation) {
if (!mSetupDone) {
logError("Illegal state for operation (" + operation + "): IAB helper is not set up.");
throw new IllegalStateException("IAB helper is not set up. Can't perform operation: " + operation);
}
}
// Workaround to bug where sometimes response codes come as Long instead of Integer
int getResponseCodeFromBundle(Bundle b) {
Object o = b.get(RESPONSE_CODE);
if (o == null) {
logDebug("Bundle with null response code, assuming OK (known issue)");
return BILLING_RESPONSE_RESULT_OK;
}
else if (o instanceof Integer) return ((Integer)o).intValue();
else if (o instanceof Long) return (int)((Long)o).longValue();
else {
logError("Unexpected type for bundle response code.");
logError(o.getClass().getName());
throw new RuntimeException("Unexpected type for bundle response code: " + o.getClass().getName());
}
}
// Workaround to bug where sometimes response codes come as Long instead of Integer
int getResponseCodeFromIntent(Intent i) {
Object o = i.getExtras().get(RESPONSE_CODE);
if (o == null) {
logError("Intent with no response code, assuming OK (known issue)");
return BILLING_RESPONSE_RESULT_OK;
}
else if (o instanceof Integer) return ((Integer)o).intValue();
else if (o instanceof Long) return (int)((Long)o).longValue();
else {
logError("Unexpected type for intent response code.");
logError(o.getClass().getName());
throw new RuntimeException("Unexpected type for intent response code: " + o.getClass().getName());
}
}
void flagStartAsync(String operation) {
try {
if (mAsyncInProgress) throw new IllegalStateException("Can't start async operation (" +
operation + ") because another async operation(" + mAsyncOperation + ") is in progress.");
mAsyncOperation = operation;
mAsyncInProgress = true;
logDebug("Starting async operation: " + operation);
}catch (Exception ignored){
}
}
void flagEndAsync() {
logDebug("Ending async operation: " + mAsyncOperation);
mAsyncOperation = "";
mAsyncInProgress = false;
}
int queryPurchases(util.Inventory inv, String itemType) throws JSONException, RemoteException {
try {
// Query purchases
logDebug("Querying owned items, item type: " + itemType);
logDebug("Package name: " + mContext.getPackageName());
boolean verificationFailed = false;
String continueToken = null;
do {
logDebug("Calling getPurchases with continuation token: " + continueToken);
Bundle ownedItems = mService.getPurchases(3, mContext.getPackageName(),
itemType, continueToken);
int response = getResponseCodeFromBundle(ownedItems);
logDebug("Owned items response: " + String.valueOf(response));
if (response != BILLING_RESPONSE_RESULT_OK) {
logDebug("getPurchases() failed: " + getResponseDesc(response));
return response;
}
if (!ownedItems.containsKey(RESPONSE_INAPP_ITEM_LIST)
|| !ownedItems.containsKey(RESPONSE_INAPP_PURCHASE_DATA_LIST)
|| !ownedItems.containsKey(RESPONSE_INAPP_SIGNATURE_LIST)) {
logError("Bundle returned from getPurchases() doesn't contain required fields.");
return IABHELPER_BAD_RESPONSE;
}
ArrayList<String> ownedSkus = ownedItems.getStringArrayList(
RESPONSE_INAPP_ITEM_LIST);
ArrayList<String> purchaseDataList = ownedItems.getStringArrayList(
RESPONSE_INAPP_PURCHASE_DATA_LIST);
ArrayList<String> signatureList = ownedItems.getStringArrayList(
RESPONSE_INAPP_SIGNATURE_LIST);
for (int i = 0; i < purchaseDataList.size(); ++i) {
String purchaseData = purchaseDataList.get(i);
String signature = signatureList.get(i);
String sku = ownedSkus.get(i);
if (util.Security.verifyPurchase(mSignatureBase64, purchaseData, signature)) {
logDebug("Sku is owned: " + sku);
util.Purchase purchase = new Purchase(itemType, purchaseData, signature);
if (TextUtils.isEmpty(purchase.getToken())) {
logWarn("BUG: empty/null token!");
logDebug("Purchase data: " + purchaseData);
}
// Record ownership and token
inv.addPurchase(purchase);
}
else {
logWarn("Purchase signature verification **FAILED**. Not adding item.");
logDebug(" Purchase data: " + purchaseData);
logDebug(" Signature: " + signature);
verificationFailed = true;
}
}
continueToken = ownedItems.getString(INAPP_CONTINUATION_TOKEN);
logDebug("Continuation token: " + continueToken);
} while (!TextUtils.isEmpty(continueToken));
return verificationFailed ? IABHELPER_VERIFICATION_FAILED : BILLING_RESPONSE_RESULT_OK;
}catch (Exception ignored){
}
return 0;
}
int querySkuDetails(String itemType, util.Inventory inv, List<String> moreSkus)
throws RemoteException, JSONException {
logDebug("Querying SKU details.");
ArrayList<String> skuList = new ArrayList<String>();
skuList.addAll(inv.getAllOwnedSkus(itemType));
if (moreSkus != null) {
for (String sku : moreSkus) {
if (!skuList.contains(sku)) {
skuList.add(sku);
}
}
}
if (skuList.size() == 0) {
logDebug("queryPrices: nothing to do because there are no SKUs.");
return BILLING_RESPONSE_RESULT_OK;
}
Bundle querySkus = new Bundle();
querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, skuList);
Bundle skuDetails=null;
try {
skuDetails = mService.getSkuDetails(3, mContext.getPackageName(), itemType, querySkus);
}catch (Exception e){
}
if (skuDetails!=null&&!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
int response = getResponseCodeFromBundle(skuDetails);
if (response != BILLING_RESPONSE_RESULT_OK) {
logDebug("getSkuDetails() failed: " + getResponseDesc(response));
return response;
}
else {
logError("getSkuDetails() returned a bundle with neither an error nor a detail list.");
return IABHELPER_BAD_RESPONSE;
}
}
try {
ArrayList<String> responseList = skuDetails.getStringArrayList(RESPONSE_GET_SKU_DETAILS_LIST);
for (String thisResponse : responseList) {
util.SkuDetails d = new SkuDetails(itemType, thisResponse);
logDebug("Got sku details: " + d);
inv.addSkuDetails(d);
}
}catch (Exception e){
}
return BILLING_RESPONSE_RESULT_OK;
}
void consumeAsyncInternal(final List<util.Purchase> purchases,
final OnConsumeFinishedListener singleListener,
final OnConsumeMultiFinishedListener multiListener) {
final Handler handler = new Handler();
flagStartAsync("consume");
(new Thread(new Runnable() {
public void run() {
final List<util.IabResult> results = new ArrayList<IabResult>();
for (util.Purchase purchase : purchases) {
try {
consume(purchase);
results.add(new util.IabResult(BILLING_RESPONSE_RESULT_OK, "Successful consume of sku " + purchase.getSku()));
}
catch (IabException ex) {
results.add(ex.getResult());
}
}
flagEndAsync();
if (!mDisposed && singleListener != null) {
handler.post(new Runnable() {
public void run() {
singleListener.onConsumeFinished(purchases.get(0), results.get(0));
}
});
}
if (!mDisposed && multiListener != null) {
handler.post(new Runnable() {
public void run() {
multiListener.onConsumeMultiFinished(purchases, results);
}
});
}
}
})).start();
}
void logDebug(String msg) {
if (mDebugLog) Log.d(mDebugTag, msg);
}
void logError(String msg) {
Log.e(mDebugTag, "In-app billing error: " + msg);
}
void logWarn(String msg) {
Log.w(mDebugTag, "In-app billing warning: " + msg);
}
}
پاسخگویی و مشاهده پاسخ های این سوال تنها برای اعضای ویژه سایت امکان پذیر است .
چنانچه تمایل دارید به همه بخش ها دسترسی داشته باشید میتوانید از این بخش لایسنس این آموزش را خریداری نمایید .