Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
(new Thread(new Runnable() {
public void run() {
final List<IabResult> results = new ArrayList<IabResult>();
for (Purchase purchase : purchases) {
try {
consume(purchase);
results.add(new IabResult(BILLING_RESPONSE_RESULT_OK, "Successful consume of sku " + purchase.getSku()));
} catch (IabException ex) {
results.add(ex.getResult());
}
}
flagEndAsync();
if (setupState != SETUP_DISPOSED && singleListener != null) {
notifyHandler.post(new Runnable() {
public void run() {
singleListener.onConsumeFinished(purchases.get(0), results.get(0));
}
});
}
if (setupState != SETUP_DISPOSED && multiListener != null) {
notifyHandler.post(new Runnable() {
public void run() {
multiListener.onConsumeMultiFinished(purchases, results);
}
});
}
}
})).start();
IInAppBillingService. Если на момент выполнения биллинг запроса сервис не подключён — запрос встаёт в очередь и ждёт подключения.Activity.Activity.Activity становится проще за счёт того, что код биллинга находится в отдельных классах.Activity. Т.е. загрузить данные о покупках, например, в Application или Service просто невозможно.Activity происходит переподключение биллинг сервиса, что не эффективно, т.к. Activity убивается каждый раз при повороте экрана (у меня он подключается один раз при старте приложения).Activity. Если пользователь повернёт экран, то Activity убивается, но слушатель, который был создан остаётся. При вызове методов последнего скорее всего приложение навернётся, т.к. Activity уже разрушено.consumePurchase и другие методы следует вызывать на отдельном потоке (см. доки). В этой библиотеки они вызываются на основном потоке, что может привести к неотзывчивому интерфейсу.dependencies {
...
compile 'com.google.code.findbugs:jsr305:3.0.1'
}
buildTypes {
debug {
buildConfigField "Boolean", "DEBUG_MODE", "true"
}
release {
buildConfigField "Boolean", "DEBUG_MODE", "false"
}
}
transitive = true
для зависимостиcompile ('com.google.code.findbugs:jsr305:2.0.3') {
transitive = true
}
compile ('org.solovyev.android:checkout:x.x.x@aar') {
transitive = true
}
mCheckout.start();
mCheckout.createPurchaseFlow(new PurchaseListener());
mInventory = mCheckout.loadInventory();
mInventory.whenLoaded(new InventoryLoadedListener());
mCheckout.start();
mCheckout.createPurchaseFlow(new PurchaseListener());
mInventory = mCheckout.makeInventory();
mInventory.load(Inventory.Request.create()
.loadAllPurchases()
.loadSkus(ProductTypes.IN_APP, "sku_01"), new InventoryCallback());
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import org.solovyev.android.checkout.ActivityCheckout;
import org.solovyev.android.checkout.BillingRequests;
import org.solovyev.android.checkout.Checkout;
import org.solovyev.android.checkout.EmptyRequestListener;
import org.solovyev.android.checkout.Inventory;
import org.solovyev.android.checkout.ProductTypes;
import org.solovyev.android.checkout.Purchase;
import org.solovyev.android.checkout.RequestListener;
import org.solovyev.android.checkout.ResponseCodes;
import org.solovyev.android.checkout.Sku;
import java.util.Locale;
import javax.annotation.Nonnull;
public abstract class InAppTemplateActivity extends Activity {
public final static String SKU = "sku_1";
private Inventory.Request mInventoryRequest;
private final InventoryCallback mInventoryCallback = new InventoryCallback();
private ActivityCheckout mCheckout;
private Inventory mInventory;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCheckout = Checkout.forActivity(this, application.inApp.getBilling());
mCheckout.start(new Checkout.Listener() {
public void onReady(@Nonnull BillingRequests requests) {
Log.i(this.getClass().toString(), "Checkout onReady");
}
public void onReady(@Nonnull BillingRequests requests, @Nonnull String product, boolean billingSupported) {
}
});
mCheckout.createPurchaseFlow(new PurchaseListener());
mInventory = mCheckout.makeInventory();
mInventoryRequest = Inventory.Request.create();
// load purchase info
mInventoryRequest.loadAllPurchases();
// load SKU details
mInventoryRequest.loadSkus(ProductTypes.IN_APP, "android.test.purchased", SKU);
reloadInventory();
}
@Override
protected void onDestroy() {
mCheckout.stop();
mCheckout = null;
mInventory = null;
super.onDestroy();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mCheckout.onActivityResult(requestCode, resultCode, data);
super.onActivityResult(requestCode, resultCode, data);
}
protected void purchase(final String sku) {
mCheckout.whenReady(new Checkout.EmptyListener() {
@Override
public void onReady(BillingRequests requests) {
requests.purchase(ProductTypes.IN_APP, sku, null, mCheckout.getPurchaseFlow());
}
});
}
private void reloadInventory() {
mInventory.load(mInventoryRequest, mInventoryCallback);
}
private void consume(final Purchase purchase) {
mCheckout.whenReady(new Checkout.EmptyListener() {
@Override
public void onReady(@Nonnull BillingRequests requests) {
requests.consume(purchase.token, new RequestListener<Object>() {
@Override
public void onSuccess(@Nonnull Object result) {
Log.i(this.getClass().toString(), "Consume onSuccess");
consumed();
reloadInventory();
}
@Override
public void onError(int response, @Nonnull Exception e) {
// it is possible that our data is not synchronized with data on Google Play => need to handle some errors
if (response == ResponseCodes.ITEM_NOT_OWNED) {
Log.i(this.getClass().toString(), "ERROR: ITEM_NOT_OWNED");
consumed();
} else {
Log.e(this.getClass().toString(), "ERROR: " + e.toString());
}
reloadInventory();
}
private void consumed() {
// если нужно, сделать что-то после потребления покупки
}
});
}
});
}
// Чтобы в оплате проходили Static Responses (у них нет токена) нужно заменить дефолтный PurchaseVerifier на свой,
// например, такой:
/* class MyPurchaseVerifier implements PurchaseVerifier {
@Nonnull
private final String mPublicKey;
public MyPurchaseVerifier(@Nonnull String publicKey) {
mPublicKey = publicKey;
}
@Override
public void verify(@Nonnull List<Purchase> purchases, @Nonnull RequestListener<List<Purchase>> listener) {
final List<Purchase> verifiedPurchases = new ArrayList<Purchase>(purchases.size());
for (Purchase purchase : purchases) {
if ("android.test.purchased;android.test.canceled;android.test.refunded;android.test.item_unavailable".contains(purchase.sku)) {
verifiedPurchases.add(purchase);
} else if (Security.verifyPurchase(mPublicKey, purchase.data, purchase.signature)) {
verifiedPurchases.add(purchase);
} else {
if (isEmpty(purchase.signature)) {
Log.e(this.getClass().toString(), "Cannot verify purchase: " + purchase + ". Signature is empty");
} else {
Log.e(this.getClass().toString(), "Cannot verify purchase: " + purchase + ". Wrong signature");
}
}
}
listener.onSuccess(verifiedPurchases);
}
}*/
// И вернуть его в new Billing.DefaultConfiguration()
/*
@Nonnull
@Override
public PurchaseVerifier getPurchaseVerifier() {
return new MyPurchaseVerifier(getPublicKey());
}
*/
private class PurchaseListener extends EmptyRequestListener<Purchase> {
@Override
public void onSuccess(@Nonnull Purchase purchase) {
Log.i(this.getClass().toString(), "Purchase onSuccess");
purchased();
reloadInventory();
}
@Override
public void onError(int response, @Nonnull Exception e) {
// it is possible that our data is not synchronized with data on Google Play => need to handle some errors
if (response == ResponseCodes.ITEM_ALREADY_OWNED) {
Log.i(this.getClass().toString(), "ERROR: ITEM_ALREADY_OWNED");
purchased();
} else {
Log.e(this.getClass().toString(), "ERROR: " + e.toString());
}
reloadInventory();
}
private void purchased() {
// сделать что-то после покупки
}
}
private class InventoryCallback implements Inventory.Callback {
@Override
public void onLoaded(Inventory.Products products) {
final Inventory.Product product = products.get(ProductTypes.IN_APP);
if (!product.supported) {
// billing is not supported, user can't purchase anything. Don't show ads in this case
Log.e(this.getClass().toString(), "billing_not_supported");
return;
}
for (Sku sku : product.getSkus()) {
Log.i(this.getClass().toString(),
String.format(Locale.US, "SKU: id = %s, title = %s, price = %s, currency = %s",
sku.id,
sku.title,
sku.detailedPrice.amount/1000000,
sku.detailedPrice.currency
)
);
final Purchase purchase = product.getPurchaseInState(sku, Purchase.State.PURCHASED);
if (purchase != null) {
Log.i(this.getClass().toString(), String.format(Locale.US, "Куплено - SKU: id = %s", sku.id));
if (purchase.token != null) {
// token есть только у непотреблённых покупок
Log.i(this.getClass().toString(),
String.format(Locale.US, "Необходимо потребить товар - SKU: id = %s, token = %s, Google Wallet Order ID = %s, payload = %s",
sku.id,
purchase.token,
purchase.orderId != null ? purchase.orderId : "null",// У static responses orderId отсутствует
purchase.payload
)
);
consume(purchase);
}
}
}
}
}
}
Библиотека для совершения покупок внутри приложений (Android In-App Billing v.3)