Updated IAB

pull/207/head
M66B 4 years ago
parent f1443332f6
commit 30a995323f

@ -343,17 +343,17 @@ dependencies {
def lbm_version = "1.1.0" def lbm_version = "1.1.0"
def swiperefresh_version = "1.2.0-alpha01" def swiperefresh_version = "1.2.0-alpha01"
def documentfile_version = "1.1.0-alpha01" def documentfile_version = "1.1.0-alpha01"
def lifecycle_version = "2.4.0" // 2.5.0-alpha03 def lifecycle_version = "2.4.0" // 2.5.0-alpha04
def lifecycle_extensions_version = "2.2.0" def lifecycle_extensions_version = "2.2.0"
def room_version = "2.4.2" // 2.5.0-alpha01 def room_version = "2.4.2" // 2.5.0-alpha01
def sqlite_version = "2.2.0" // 2.3.0-alpha01 def sqlite_version = "2.2.0" // 2.3.0-alpha01
def requery_version = "3.36.0" def requery_version = "3.36.0"
def paging_version = "2.1.2" // 3.1.0 def paging_version = "2.1.2" // 3.1.1
def preference_version = "1.2.0" def preference_version = "1.2.0"
def work_version = "2.7.1" // 2.8.0-alpha01 def work_version = "2.7.1" // 2.8.0-alpha01
def exif_version = "1.3.3" def exif_version = "1.3.3"
def biometric_version = "1.2.0-alpha04" def biometric_version = "1.2.0-alpha04"
def billingclient_version = "3.0.3" // 4.0.0 def billingclient_version = "4.1.0"
def javamail_version = "1.6.7" def javamail_version = "1.6.7"
def jsoup_version = "1.14.3" def jsoup_version = "1.14.3"
def css_version = "0.9.29" def css_version = "0.9.29"

@ -52,6 +52,7 @@ import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ConsumeParams; import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener; import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.Purchase; import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesResponseListener;
import com.android.billingclient.api.PurchasesUpdatedListener; import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails; import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams; import com.android.billingclient.api.SkuDetailsParams;
@ -155,16 +156,19 @@ public class ActivityBilling extends ActivityBase implements /*PurchasesUpdatedL
} }
@NonNull @NonNull
static String getSkuPro() { static String getSkuPro(Context context) {
if (BuildConfig.DEBUG) if (isTesting(context))
return SKU_TEST; return SKU_TEST;
else else
return BuildConfig.APPLICATION_ID + ".pro"; return BuildConfig.APPLICATION_ID + ".pro";
} }
static boolean isTesting(Context context) { static boolean isTesting(Context context) {
if (context == null)
return false;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return (BuildConfig.TEST_RELEASE && prefs.getBoolean("test_iab", false)); return (BuildConfig.DEBUG && BuildConfig.TEST_RELEASE &&
prefs.getBoolean("test_iab", false));
} }
private static String getChallenge(Context context) throws NoSuchAlgorithmException { private static String getChallenge(Context context) throws NoSuchAlgorithmException {
@ -230,7 +234,7 @@ public class ActivityBilling extends ActivityBase implements /*PurchasesUpdatedL
private void onPurchase(Intent intent) { private void onPurchase(Intent intent) {
if (Helper.isPlayStoreInstall() || isTesting(this)) { if (Helper.isPlayStoreInstall() || isTesting(this)) {
String skuPro = getSkuPro(); String skuPro = getSkuPro(this);
Log.i("IAB purchase SKU=" + skuPro); Log.i("IAB purchase SKU=" + skuPro);
/* /*
SkuDetailsParams.Builder builder = SkuDetailsParams.newBuilder(); SkuDetailsParams.Builder builder = SkuDetailsParams.newBuilder();
@ -271,12 +275,16 @@ public class ActivityBilling extends ActivityBase implements /*PurchasesUpdatedL
} }
/* /*
private void onPurchaseConsume(Intent intent) { private void onPurchaseConsume(Intent intent) {
Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.INAPP); billingClient.queryPurchasesAsync(BillingClient.SkuType.INAPP, new PurchasesResponseListener() {
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) { @Override
for (Purchase purchase : result.getPurchasesList()) public void onQueryPurchasesResponse(@NonNull BillingResult result, @NonNull List<Purchase> list) {
consumePurchase(purchase); if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
} else for (Purchase purchase : list)
reportError(result.getBillingResult(), "IAB onPurchaseConsume"); consumePurchase(purchase);
} else
reportError(result, "IAB onPurchaseConsume");
}
});
} }
private void onPurchaseError(Intent intent) { private void onPurchaseError(Intent intent) {
@ -336,6 +344,7 @@ public class ActivityBilling extends ActivityBase implements /*PurchasesUpdatedL
@Override @Override
public void onPurchasesUpdated(BillingResult result, @Nullable List<Purchase> purchases) { public void onPurchasesUpdated(BillingResult result, @Nullable List<Purchase> purchases) {
Log.i("IAB purchases updated");
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) if (result.getResponseCode() == BillingClient.BillingResponseCode.OK)
checkPurchases(purchases); checkPurchases(purchases);
else else
@ -343,11 +352,16 @@ public class ActivityBilling extends ActivityBase implements /*PurchasesUpdatedL
} }
private void queryPurchases() { private void queryPurchases() {
Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.INAPP); billingClient.queryPurchasesAsync(BillingClient.SkuType.INAPP, new PurchasesResponseListener() {
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) @Override
checkPurchases(result.getPurchasesList()); public void onQueryPurchasesResponse(@NonNull BillingResult result, @NonNull List<Purchase> list) {
else if (result.getResponseCode() == BillingClient.BillingResponseCode.OK)
reportError(result.getBillingResult(), "IAB query purchases"); checkPurchases(list);
else
reportError(result, "IAB query purchases");
}
});
} }
*/ */
interface IBillingListener { interface IBillingListener {
@ -388,13 +402,13 @@ public class ActivityBilling extends ActivityBase implements /*PurchasesUpdatedL
Log.i("IAB purchases=" + (purchases == null ? null : purchases.size())); Log.i("IAB purchases=" + (purchases == null ? null : purchases.size()));
List<String> query = new ArrayList<>(); List<String> query = new ArrayList<>();
query.add(getSkuPro()); query.add(getSkuPro(this));
if (purchases != null) { if (purchases != null) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = prefs.edit(); SharedPreferences.Editor editor = prefs.edit();
if (prefs.getBoolean("play_store", true)) { if (prefs.getBoolean("play_store", true)) {
long cached = prefs.getLong(getSkuPro() + ".cached", 0); long cached = prefs.getLong(getSkuPro(this) + ".cached", 0);
if (cached + MAX_SKU_CACHE_DURATION < new Date().getTime()) { if (cached + MAX_SKU_CACHE_DURATION < new Date().getTime()) {
Log.i("IAB cache expired=" + new Date(cached)); Log.i("IAB cache expired=" + new Date(cached));
editor.remove("pro"); editor.remove("pro");
@ -403,50 +417,51 @@ public class ActivityBilling extends ActivityBase implements /*PurchasesUpdatedL
} }
for (Purchase purchase : purchases) for (Purchase purchase : purchases)
try { for (String sku : purchase.getSkus())
query.remove(purchase.getSku()); try {
query.remove(sku);
long time = purchase.getPurchaseTime();
Log.i("IAB SKU=" + purchase.getSku() + long time = purchase.getPurchaseTime();
" purchased=" + isPurchased(purchase) + Log.i("IAB SKU=" + sku +
" valid=" + isPurchaseValid(purchase) + " purchased=" + isPurchased(purchase) +
" time=" + new Date(time)); " valid=" + isPurchaseValid(purchase) +
Log.i("IAB json=" + purchase.getOriginalJson()); " time=" + new Date(time));
Log.i("IAB json=" + purchase.getOriginalJson());
for (IBillingListener listener : listeners)
if (isPurchaseValid(purchase)) for (IBillingListener listener : listeners)
listener.onPurchased(purchase.getSku(), true); if (isPurchaseValid(purchase))
else listener.onPurchased(sku, true);
listener.onPurchasePending(purchase.getSku()); else
listener.onPurchasePending(sku);
if (isPurchased(purchase)) {
byte[] decodedKey = Base64.decode(getString(R.string.public_key), Base64.DEFAULT); if (isPurchased(purchase)) {
KeyFactory keyFactory = KeyFactory.getInstance("RSA"); byte[] decodedKey = Base64.decode(getString(R.string.public_key), Base64.DEFAULT);
PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey)); KeyFactory keyFactory = KeyFactory.getInstance("RSA");
Signature sig = Signature.getInstance("SHA1withRSA"); PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
sig.initVerify(publicKey); Signature sig = Signature.getInstance("SHA1withRSA");
sig.update(purchase.getOriginalJson().getBytes()); sig.initVerify(publicKey);
if (SKU_TEST.equals(purchase.getSku()) || sig.update(purchase.getOriginalJson().getBytes());
sig.verify(Base64.decode(purchase.getSignature(), Base64.DEFAULT))) { if (SKU_TEST.equals(sku) ||
Log.i("IAB valid signature"); sig.verify(Base64.decode(purchase.getSignature(), Base64.DEFAULT))) {
if (getSkuPro().equals(purchase.getSku())) { Log.i("IAB valid signature");
if (isPurchaseValid(purchase)) { if (getSkuPro(this).equals(sku)) {
editor.putBoolean("pro", true); if (isPurchaseValid(purchase)) {
editor.putLong(purchase.getSku() + ".cached", new Date().getTime()); editor.putBoolean("pro", true);
editor.putLong(sku + ".cached", new Date().getTime());
}
if (!purchase.isAcknowledged())
acknowledgePurchase(purchase, 0);
} }
} else {
if (!purchase.isAcknowledged()) Log.w("IAB invalid signature");
acknowledgePurchase(purchase, 0); editor.putBoolean("pro", false);
reportError(null, "Invalid purchase");
} }
} else {
Log.w("IAB invalid signature");
editor.putBoolean("pro", false);
reportError(null, "Invalid purchase");
} }
} catch (Throwable ex) {
reportError(null, Log.formatThrowable(ex, false));
} }
} catch (Throwable ex) {
reportError(null, Log.formatThrowable(ex, false));
}
editor.apply(); editor.apply();
@ -479,55 +494,59 @@ public class ActivityBilling extends ActivityBase implements /*PurchasesUpdatedL
} }
private void consumePurchase(final Purchase purchase) { private void consumePurchase(final Purchase purchase) {
Log.i("IAB consuming SKU=" + purchase.getSku()); for (String sku : purchase.getSkus()) {
ConsumeParams params = ConsumeParams.newBuilder() Log.i("IAB consuming SKU=" + sku);
.setPurchaseToken(purchase.getPurchaseToken()) ConsumeParams params = ConsumeParams.newBuilder()
.build(); .setPurchaseToken(purchase.getPurchaseToken())
billingClient.consumeAsync(params, new ConsumeResponseListener() { .build();
@Override billingClient.consumeAsync(params, new ConsumeResponseListener() {
public void onConsumeResponse(@NonNull BillingResult result, @NonNull String purchaseToken) { @Override
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) { public void onConsumeResponse(@NonNull BillingResult result, @NonNull String purchaseToken) {
for (IBillingListener listener : listeners) if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
listener.onPurchased(purchase.getSku(), false); for (IBillingListener listener : listeners)
} else listener.onPurchased(sku, false);
reportError(result, "IAB consuming SKU=" + purchase.getSku()); } else
} reportError(result, "IAB consuming SKU=" + sku);
}); }
});
}
} }
private void acknowledgePurchase(final Purchase purchase, int retry) { private void acknowledgePurchase(final Purchase purchase, int retry) {
Log.i("IAB acknowledging purchase SKU=" + purchase.getSku()); for (String sku : purchase.getSkus()) {
AcknowledgePurchaseParams params = Log.i("IAB acknowledging purchase SKU=" + sku);
AcknowledgePurchaseParams.newBuilder() AcknowledgePurchaseParams params =
.setPurchaseToken(purchase.getPurchaseToken()) AcknowledgePurchaseParams.newBuilder()
.build(); .setPurchaseToken(purchase.getPurchaseToken())
billingClient.acknowledgePurchase(params, new AcknowledgePurchaseResponseListener() { .build();
@Override billingClient.acknowledgePurchase(params, new AcknowledgePurchaseResponseListener() {
public void onAcknowledgePurchaseResponse(@NonNull BillingResult result) { @Override
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) { public void onAcknowledgePurchaseResponse(@NonNull BillingResult result) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ActivityBilling.this); if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
SharedPreferences.Editor editor = prefs.edit(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ActivityBilling.this);
editor.putBoolean("pro", true); SharedPreferences.Editor editor = prefs.edit();
editor.putLong(purchase.getSku() + ".cached", new Date().getTime()); editor.putBoolean("pro", true);
editor.apply(); editor.putLong(sku + ".cached", new Date().getTime());
editor.apply();
for (IBillingListener listener : listeners)
listener.onPurchased(purchase.getSku(), true); for (IBillingListener listener : listeners)
listener.onPurchased(sku, true);
WidgetUnified.updateData(ActivityBilling.this);
} else { WidgetUnified.updateData(ActivityBilling.this);
if (retry < 3) { } else {
new Handler().postDelayed(new Runnable() { if (retry < 3) {
@Override new Handler().postDelayed(new Runnable() {
public void run() { @Override
acknowledgePurchase(purchase, retry + 1); public void run() {
} acknowledgePurchase(purchase, retry + 1);
}, (retry + 1) * 10 * 1000L); }
} else }, (retry + 1) * 10 * 1000L);
reportError(result, "IAB acknowledged SKU=" + purchase.getSku()); } else
reportError(result, "IAB acknowledged SKU=" + sku);
}
} }
} });
}); }
} }
private boolean isPurchased(Purchase purchase) { private boolean isPurchased(Purchase purchase) {
@ -537,7 +556,7 @@ public class ActivityBilling extends ActivityBase implements /*PurchasesUpdatedL
private boolean isPurchaseValid(Purchase purchase) { private boolean isPurchaseValid(Purchase purchase) {
return (isPurchased(purchase) && return (isPurchased(purchase) &&
(purchase.isAcknowledged() || (purchase.isAcknowledged() ||
SKU_TEST.equals(purchase.getSku()) || purchase.getSkus().contains(SKU_TEST) ||
purchase.getPurchaseTime() + MAX_SKU_NOACK_DURATION > new Date().getTime())); purchase.getPurchaseTime() + MAX_SKU_NOACK_DURATION > new Date().getTime()));
} }

@ -1444,7 +1444,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
(external == null ? null : external.getAbsolutePath()) + (emulated ? " emulated" : "")); (external == null ? null : external.getAbsolutePath()) + (emulated ? " emulated" : ""));
swExactAlarms.setEnabled(AlarmManagerCompatEx.canScheduleExactAlarms(getContext())); swExactAlarms.setEnabled(AlarmManagerCompatEx.canScheduleExactAlarms(getContext()));
swTestIab.setVisibility(BuildConfig.TEST_RELEASE ? View.VISIBLE : View.GONE); swTestIab.setVisibility(BuildConfig.DEBUG && BuildConfig.TEST_RELEASE ? View.VISIBLE : View.GONE);
PreferenceManager.getDefaultSharedPreferences(getContext()).registerOnSharedPreferenceChangeListener(this); PreferenceManager.getDefaultSharedPreferences(getContext()).registerOnSharedPreferenceChangeListener(this);

@ -221,7 +221,7 @@ public class FragmentPro extends FragmentBase implements SharedPreferences.OnSha
@Override @Override
public void onSkuDetails(String sku, String price) { public void onSkuDetails(String sku, String price) {
if (!ActivityBilling.getSkuPro().equals(sku)) if (!ActivityBilling.getSkuPro(getContext()).equals(sku))
return; return;
post(new Runnable() { post(new Runnable() {
@ -236,7 +236,7 @@ public class FragmentPro extends FragmentBase implements SharedPreferences.OnSha
@Override @Override
public void onPurchasePending(String sku) { public void onPurchasePending(String sku) {
if (!ActivityBilling.getSkuPro().equals(sku)) if (!ActivityBilling.getSkuPro(getContext()).equals(sku))
return; return;
post(new Runnable() { post(new Runnable() {
@ -250,7 +250,7 @@ public class FragmentPro extends FragmentBase implements SharedPreferences.OnSha
@Override @Override
public void onPurchased(String sku, boolean purchased) { public void onPurchased(String sku, boolean purchased) {
if (!ActivityBilling.getSkuPro().equals(sku)) if (!ActivityBilling.getSkuPro(getContext()).equals(sku))
return; return;
post(new Runnable() { post(new Runnable() {

@ -52,6 +52,7 @@ import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ConsumeParams; import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener; import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.Purchase; import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesResponseListener;
import com.android.billingclient.api.PurchasesUpdatedListener; import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails; import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams; import com.android.billingclient.api.SkuDetailsParams;
@ -155,16 +156,19 @@ public class ActivityBilling extends ActivityBase implements PurchasesUpdatedLis
} }
@NonNull @NonNull
static String getSkuPro() { static String getSkuPro(Context context) {
if (BuildConfig.DEBUG) if (isTesting(context))
return SKU_TEST; return SKU_TEST;
else else
return BuildConfig.APPLICATION_ID + ".pro"; return BuildConfig.APPLICATION_ID + ".pro";
} }
static boolean isTesting(Context context) { static boolean isTesting(Context context) {
if (context == null)
return false;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return (BuildConfig.TEST_RELEASE && prefs.getBoolean("test_iab", false)); return (BuildConfig.DEBUG && BuildConfig.TEST_RELEASE &&
prefs.getBoolean("test_iab", false));
} }
private static String getChallenge(Context context) throws NoSuchAlgorithmException { private static String getChallenge(Context context) throws NoSuchAlgorithmException {
@ -230,7 +234,7 @@ public class ActivityBilling extends ActivityBase implements PurchasesUpdatedLis
private void onPurchase(Intent intent) { private void onPurchase(Intent intent) {
if (Helper.isPlayStoreInstall() || isTesting(this)) { if (Helper.isPlayStoreInstall() || isTesting(this)) {
String skuPro = getSkuPro(); String skuPro = getSkuPro(this);
Log.i("IAB purchase SKU=" + skuPro); Log.i("IAB purchase SKU=" + skuPro);
SkuDetailsParams.Builder builder = SkuDetailsParams.newBuilder(); SkuDetailsParams.Builder builder = SkuDetailsParams.newBuilder();
@ -270,12 +274,16 @@ public class ActivityBilling extends ActivityBase implements PurchasesUpdatedLis
} }
private void onPurchaseConsume(Intent intent) { private void onPurchaseConsume(Intent intent) {
Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.INAPP); billingClient.queryPurchasesAsync(BillingClient.SkuType.INAPP, new PurchasesResponseListener() {
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) { @Override
for (Purchase purchase : result.getPurchasesList()) public void onQueryPurchasesResponse(@NonNull BillingResult result, @NonNull List<Purchase> list) {
consumePurchase(purchase); if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
} else for (Purchase purchase : list)
reportError(result.getBillingResult(), "IAB onPurchaseConsume"); consumePurchase(purchase);
} else
reportError(result, "IAB onPurchaseConsume");
}
});
} }
private void onPurchaseError(Intent intent) { private void onPurchaseError(Intent intent) {
@ -335,6 +343,7 @@ public class ActivityBilling extends ActivityBase implements PurchasesUpdatedLis
@Override @Override
public void onPurchasesUpdated(BillingResult result, @Nullable List<Purchase> purchases) { public void onPurchasesUpdated(BillingResult result, @Nullable List<Purchase> purchases) {
Log.i("IAB purchases updated");
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) if (result.getResponseCode() == BillingClient.BillingResponseCode.OK)
checkPurchases(purchases); checkPurchases(purchases);
else else
@ -342,11 +351,16 @@ public class ActivityBilling extends ActivityBase implements PurchasesUpdatedLis
} }
private void queryPurchases() { private void queryPurchases() {
Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.INAPP); billingClient.queryPurchasesAsync(BillingClient.SkuType.INAPP, new PurchasesResponseListener() {
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) @Override
checkPurchases(result.getPurchasesList()); public void onQueryPurchasesResponse(@NonNull BillingResult result, @NonNull List<Purchase> list) {
else if (result.getResponseCode() == BillingClient.BillingResponseCode.OK)
reportError(result.getBillingResult(), "IAB query purchases"); checkPurchases(list);
else
reportError(result, "IAB query purchases");
}
});
} }
interface IBillingListener { interface IBillingListener {
@ -387,13 +401,13 @@ public class ActivityBilling extends ActivityBase implements PurchasesUpdatedLis
Log.i("IAB purchases=" + (purchases == null ? null : purchases.size())); Log.i("IAB purchases=" + (purchases == null ? null : purchases.size()));
List<String> query = new ArrayList<>(); List<String> query = new ArrayList<>();
query.add(getSkuPro()); query.add(getSkuPro(this));
if (purchases != null) { if (purchases != null) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = prefs.edit(); SharedPreferences.Editor editor = prefs.edit();
if (prefs.getBoolean("play_store", true)) { if (prefs.getBoolean("play_store", true)) {
long cached = prefs.getLong(getSkuPro() + ".cached", 0); long cached = prefs.getLong(getSkuPro(this) + ".cached", 0);
if (cached + MAX_SKU_CACHE_DURATION < new Date().getTime()) { if (cached + MAX_SKU_CACHE_DURATION < new Date().getTime()) {
Log.i("IAB cache expired=" + new Date(cached)); Log.i("IAB cache expired=" + new Date(cached));
editor.remove("pro"); editor.remove("pro");
@ -402,50 +416,51 @@ public class ActivityBilling extends ActivityBase implements PurchasesUpdatedLis
} }
for (Purchase purchase : purchases) for (Purchase purchase : purchases)
try { for (String sku : purchase.getSkus())
query.remove(purchase.getSku()); try {
query.remove(sku);
long time = purchase.getPurchaseTime();
Log.i("IAB SKU=" + purchase.getSku() + long time = purchase.getPurchaseTime();
" purchased=" + isPurchased(purchase) + Log.i("IAB SKU=" + sku +
" valid=" + isPurchaseValid(purchase) + " purchased=" + isPurchased(purchase) +
" time=" + new Date(time)); " valid=" + isPurchaseValid(purchase) +
Log.i("IAB json=" + purchase.getOriginalJson()); " time=" + new Date(time));
Log.i("IAB json=" + purchase.getOriginalJson());
for (IBillingListener listener : listeners)
if (isPurchaseValid(purchase)) for (IBillingListener listener : listeners)
listener.onPurchased(purchase.getSku(), true); if (isPurchaseValid(purchase))
else listener.onPurchased(sku, true);
listener.onPurchasePending(purchase.getSku()); else
listener.onPurchasePending(sku);
if (isPurchased(purchase)) {
byte[] decodedKey = Base64.decode(getString(R.string.public_key), Base64.DEFAULT); if (isPurchased(purchase)) {
KeyFactory keyFactory = KeyFactory.getInstance("RSA"); byte[] decodedKey = Base64.decode(getString(R.string.public_key), Base64.DEFAULT);
PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey)); KeyFactory keyFactory = KeyFactory.getInstance("RSA");
Signature sig = Signature.getInstance("SHA1withRSA"); PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
sig.initVerify(publicKey); Signature sig = Signature.getInstance("SHA1withRSA");
sig.update(purchase.getOriginalJson().getBytes()); sig.initVerify(publicKey);
if (SKU_TEST.equals(purchase.getSku()) || sig.update(purchase.getOriginalJson().getBytes());
sig.verify(Base64.decode(purchase.getSignature(), Base64.DEFAULT))) { if (SKU_TEST.equals(sku) ||
Log.i("IAB valid signature"); sig.verify(Base64.decode(purchase.getSignature(), Base64.DEFAULT))) {
if (getSkuPro().equals(purchase.getSku())) { Log.i("IAB valid signature");
if (isPurchaseValid(purchase)) { if (getSkuPro(this).equals(sku)) {
editor.putBoolean("pro", true); if (isPurchaseValid(purchase)) {
editor.putLong(purchase.getSku() + ".cached", new Date().getTime()); editor.putBoolean("pro", true);
editor.putLong(sku + ".cached", new Date().getTime());
}
if (!purchase.isAcknowledged())
acknowledgePurchase(purchase, 0);
} }
} else {
if (!purchase.isAcknowledged()) Log.w("IAB invalid signature");
acknowledgePurchase(purchase, 0); editor.putBoolean("pro", false);
reportError(null, "Invalid purchase");
} }
} else {
Log.w("IAB invalid signature");
editor.putBoolean("pro", false);
reportError(null, "Invalid purchase");
} }
} catch (Throwable ex) {
reportError(null, Log.formatThrowable(ex, false));
} }
} catch (Throwable ex) {
reportError(null, Log.formatThrowable(ex, false));
}
editor.apply(); editor.apply();
@ -478,55 +493,59 @@ public class ActivityBilling extends ActivityBase implements PurchasesUpdatedLis
} }
private void consumePurchase(final Purchase purchase) { private void consumePurchase(final Purchase purchase) {
Log.i("IAB consuming SKU=" + purchase.getSku()); for (String sku : purchase.getSkus()) {
ConsumeParams params = ConsumeParams.newBuilder() Log.i("IAB consuming SKU=" + sku);
.setPurchaseToken(purchase.getPurchaseToken()) ConsumeParams params = ConsumeParams.newBuilder()
.build(); .setPurchaseToken(purchase.getPurchaseToken())
billingClient.consumeAsync(params, new ConsumeResponseListener() { .build();
@Override billingClient.consumeAsync(params, new ConsumeResponseListener() {
public void onConsumeResponse(@NonNull BillingResult result, @NonNull String purchaseToken) { @Override
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) { public void onConsumeResponse(@NonNull BillingResult result, @NonNull String purchaseToken) {
for (IBillingListener listener : listeners) if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
listener.onPurchased(purchase.getSku(), false); for (IBillingListener listener : listeners)
} else listener.onPurchased(sku, false);
reportError(result, "IAB consuming SKU=" + purchase.getSku()); } else
} reportError(result, "IAB consuming SKU=" + sku);
}); }
});
}
} }
private void acknowledgePurchase(final Purchase purchase, int retry) { private void acknowledgePurchase(final Purchase purchase, int retry) {
Log.i("IAB acknowledging purchase SKU=" + purchase.getSku()); for (String sku : purchase.getSkus()) {
AcknowledgePurchaseParams params = Log.i("IAB acknowledging purchase SKU=" + sku);
AcknowledgePurchaseParams.newBuilder() AcknowledgePurchaseParams params =
.setPurchaseToken(purchase.getPurchaseToken()) AcknowledgePurchaseParams.newBuilder()
.build(); .setPurchaseToken(purchase.getPurchaseToken())
billingClient.acknowledgePurchase(params, new AcknowledgePurchaseResponseListener() { .build();
@Override billingClient.acknowledgePurchase(params, new AcknowledgePurchaseResponseListener() {
public void onAcknowledgePurchaseResponse(@NonNull BillingResult result) { @Override
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) { public void onAcknowledgePurchaseResponse(@NonNull BillingResult result) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ActivityBilling.this); if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
SharedPreferences.Editor editor = prefs.edit(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ActivityBilling.this);
editor.putBoolean("pro", true); SharedPreferences.Editor editor = prefs.edit();
editor.putLong(purchase.getSku() + ".cached", new Date().getTime()); editor.putBoolean("pro", true);
editor.apply(); editor.putLong(sku + ".cached", new Date().getTime());
editor.apply();
for (IBillingListener listener : listeners)
listener.onPurchased(purchase.getSku(), true); for (IBillingListener listener : listeners)
listener.onPurchased(sku, true);
WidgetUnified.updateData(ActivityBilling.this);
} else { WidgetUnified.updateData(ActivityBilling.this);
if (retry < 3) { } else {
new Handler().postDelayed(new Runnable() { if (retry < 3) {
@Override new Handler().postDelayed(new Runnable() {
public void run() { @Override
acknowledgePurchase(purchase, retry + 1); public void run() {
} acknowledgePurchase(purchase, retry + 1);
}, (retry + 1) * 10 * 1000L); }
} else }, (retry + 1) * 10 * 1000L);
reportError(result, "IAB acknowledged SKU=" + purchase.getSku()); } else
reportError(result, "IAB acknowledged SKU=" + sku);
}
} }
} });
}); }
} }
private boolean isPurchased(Purchase purchase) { private boolean isPurchased(Purchase purchase) {
@ -536,7 +555,7 @@ public class ActivityBilling extends ActivityBase implements PurchasesUpdatedLis
private boolean isPurchaseValid(Purchase purchase) { private boolean isPurchaseValid(Purchase purchase) {
return (isPurchased(purchase) && return (isPurchased(purchase) &&
(purchase.isAcknowledged() || (purchase.isAcknowledged() ||
SKU_TEST.equals(purchase.getSku()) || purchase.getSkus().contains(SKU_TEST) ||
purchase.getPurchaseTime() + MAX_SKU_NOACK_DURATION > new Date().getTime())); purchase.getPurchaseTime() + MAX_SKU_NOACK_DURATION > new Date().getTime()));
} }

Loading…
Cancel
Save