Skip to content

Instantly share code, notes, and snippets.

@Ekalips
Created October 26, 2017 11:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ekalips/f576cb1cfe014f9be43796e75b8c74e3 to your computer and use it in GitHub Desktop.
Save Ekalips/f576cb1cfe014f9be43796e75b8c74e3 to your computer and use it in GitHub Desktop.
Billing provider
package com.noctua.billing;
import android.app.Activity;
import android.support.annotation.Nullable;
import com.android.billingclient.api.Purchase;
import java.util.List;
/**
* Created by wldev on 8/28/17.
*/
public interface BillingProvider {
void connect();
void disconnect();
void addCallbackListener(BillingProviderCallbacks callbackListener);
void removeCallbackListener(BillingProviderCallbacks callbackListener);
boolean launchItemPurchase(Activity activity, String skuId);
boolean launchSubPurchase(Activity activity, String skuId, @Nullable String oldSku);
List<Purchase> getMyPurchases();
}
package com.noctua.billing;
import com.android.billingclient.api.Purchase;
import java.util.List;
/**
* Created by wldev on 8/28/17.
*/
public interface BillingProviderCallbacks {
void onBillingServiceConnected();
void onBillingServiceDisconnected();
void onBillingServiceUnAvailable();
void onError();
void onBillingFlowError();
void onUserCanceledPurchase();
void onPurchasesListUpdated(List<Purchase> purchases);
void onItemAlreadyOwned();
}
package com.noctua.billing;
import com.android.billingclient.api.Purchase;
import java.util.List;
/**
* Created by wldev on 9/13/17.
*/
public abstract class BillingProviderCallbacksAdapter implements BillingProviderCallbacks {
@Override
public void onBillingServiceConnected() {
}
@Override
public void onBillingServiceDisconnected() {
}
@Override
public void onBillingServiceUnAvailable() {
}
@Override
public void onError() {
}
@Override
public void onBillingFlowError() {
}
@Override
public void onUserCanceledPurchase() {
}
@Override
public void onPurchasesListUpdated(List<Purchase> purchases) {
}
@Override
public void onItemAlreadyOwned() {
}
}
package com.noctua.billing;
import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.text.TextUtils;
import android.util.Log;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientImpl;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.noctua.common.FuncUtils;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* Created by wldev on 8/28/17.
*/
public class BillingProviderImpl implements BillingProvider, PurchasesUpdatedListener, BillingClientStateListener {
private static final String TAG = BillingProviderImpl.class.getSimpleName();
private BillingClient mBillingClient;
private Handler uiHandler;
private final Context appContext;
private List<BillingProviderCallbacks> billingProviderCallbacks = new ArrayList<>();
public BillingProviderImpl(Context context) {
appContext = context;
mBillingClient = new BillingClient.Builder(context).setListener(this).build();
uiHandler = new Handler(Looper.getMainLooper());
}
@Override
public void connect() {
if (Looper.myLooper() == Looper.getMainLooper()) {
connectOrCreate();
} else {
connectOnUI();
}
}
@Override
public void disconnect() {
if (billingProviderCallbacks.isEmpty()) {
if (Looper.myLooper() == Looper.getMainLooper()) {
disconnectAndClear();
} else {
disconnectOnUI();
}
} else {
Log.d(TAG, "You can't disconnect service while there is at least one listener");
}
}
private void connectOrCreate() {
Log.d(TAG, "connectOrCreate: " + (mBillingClient == null));
if (mBillingClient == null) {
mBillingClient = new BillingClientImpl.Builder(appContext).setListener(this).build();
}
mBillingClient.startConnection(this);
}
private void disconnectAndClear() {
Log.d(TAG, "disconnectAndClear: " + (mBillingClient != null));
if (mBillingClient != null) {
mBillingClient.endConnection();
mBillingClient = null;
}
}
@Override
public void addCallbackListener(BillingProviderCallbacks callbackListener) {
billingProviderCallbacks.add(callbackListener);
}
@Override
public void removeCallbackListener(BillingProviderCallbacks callbackListener) {
billingProviderCallbacks.remove(callbackListener);
}
@Override
public boolean launchItemPurchase(final Activity activity, final String skuId) {
if (mBillingClient.isReady()) {
if (Looper.myLooper() == Looper.getMainLooper()) {
launchBillingFlow(activity, BillingClient.SkuType.INAPP, skuId, null);
} else {
launchItemPurchaseOnUI(activity, skuId);
}
return true;
} else {
return false;
}
}
@Override
public boolean launchSubPurchase(final Activity activity, final String skuId, @Nullable final String lastSubSku) {
if (mBillingClient.isReady()) {
if (Looper.myLooper() == Looper.getMainLooper()) {
launchBillingFlow(activity, BillingClient.SkuType.SUBS, skuId, lastSubSku);
} else {
launchSubPurchaseOnUI(activity, skuId, lastSubSku);
}
return true;
} else {
return false;
}
}
@Override
public void onBillingSetupFinished(@BillingClient.BillingResponse int resultCode) {
Log.d(TAG, "onBillingSetupFinished() called with: resultCode = [" + resultCode + "]");
if (billingProviderCallbacks != null) {
switch (resultCode) {
case BillingClient.BillingResponse.OK: {
forEachApply(new FuncUtils.MyConsumer<BillingProviderCallbacks>() {
@Override
public void accept(BillingProviderCallbacks billingProviderCallbacks) {
billingProviderCallbacks.onBillingServiceConnected();
}
});
break;
}
default: {
notifyAllListenersAboutError(resultCode);
break;
}
}
}
}
@Override
public void onBillingServiceDisconnected() {
forEachApply(new FuncUtils.MyConsumer<BillingProviderCallbacks>() {
@Override
public void accept(BillingProviderCallbacks billingProviderCallbacks) {
billingProviderCallbacks.onBillingServiceDisconnected();
}
});
}
@Override
public void onPurchasesUpdated(@BillingClient.BillingResponse int responseCode, List<Purchase> purchases) {
Log.d(TAG, "onPurchasesUpdated() called with: responseCode = [" + responseCode + "], purchases = [" + purchases + "]");
if (billingProviderCallbacks != null) {
switch (responseCode) {
case BillingClient.BillingResponse.OK: {
handlePurchases(purchases);
break;
}
default: {
notifyAllListenersAboutError(responseCode);
break;
}
}
}
}
private void handlePurchases(@Nullable final List<Purchase> purchases) {
forEachApply(new FuncUtils.MyConsumer<BillingProviderCallbacks>() {
@Override
public void accept(BillingProviderCallbacks billingProviderCallbacks) {
billingProviderCallbacks.onPurchasesListUpdated(purchases);
}
});
}
private void launchItemPurchaseOnUI(final Activity activity, final String skuId) {
final WeakReference<Activity> activityWeakReference = new WeakReference<>(activity);
final WeakReference<String> skuIDWeakReference = new WeakReference<>(skuId);
uiHandler.post(new Runnable() {
@Override
public void run() {
if (activityWeakReference.get() != null && skuIDWeakReference.get() != null) {
launchBillingFlow(activityWeakReference.get(), BillingClient.SkuType.INAPP, skuId, null);
}
}
});
}
private void launchSubPurchaseOnUI(Activity activity, final String skuId, @Nullable final String oldSku) {
final WeakReference<Activity> activityWeakReference = new WeakReference<>(activity);
final WeakReference<String> skuIDWeakReference = new WeakReference<>(skuId);
uiHandler.post(new Runnable() {
@Override
public void run() {
if (activityWeakReference.get() != null && skuIDWeakReference.get() != null) {
launchBillingFlow(activityWeakReference.get(), BillingClient.SkuType.SUBS, skuId, oldSku);
}
}
});
}
@UiThread
private void connectOnUI() {
if (!mBillingClient.isReady()) {
uiHandler.post(new Runnable() {
@Override
public void run() {
connectOrCreate();
}
});
}
}
@UiThread
private void disconnectOnUI() {
if (mBillingClient.isReady()) {
uiHandler.post(new Runnable() {
@Override
public void run() {
disconnectAndClear();
}
});
}
}
private void forEachApply(@NonNull FuncUtils.MyConsumer<? super BillingProviderCallbacks> consumer) {
for (int i = 0; i < billingProviderCallbacks.size(); i++) {
consumer.accept(billingProviderCallbacks.get(i));
}
}
@Override
public List<Purchase> getMyPurchases() {
List<Purchase> purchases = new ArrayList<>();
if (mBillingClient != null && mBillingClient.isReady()) {
Purchase.PurchasesResult inApps = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
if (inApps != null && inApps.getPurchasesList() != null) {
purchases.addAll(inApps.getPurchasesList());
}
Purchase.PurchasesResult subs = mBillingClient.queryPurchases(BillingClient.SkuType.SUBS);
if (subs != null && subs.getPurchasesList() != null) {
purchases.addAll(subs.getPurchasesList());
}
}
Log.d(TAG, "getMyPurchases() returned: " + purchases.size());
return purchases;
}
private void launchBillingFlow(Activity activity, @BillingClient.SkuType String skuType, String sku, @Nullable String oldSku) {
BillingFlowParams.Builder paramsBuilder = new BillingFlowParams.Builder().setSku(sku).setType(skuType);
if (!TextUtils.isEmpty(oldSku)) {
paramsBuilder.addOldSku(oldSku);
}
int response = mBillingClient.launchBillingFlow(activity, paramsBuilder.build());
if (response != BillingClient.BillingResponse.OK) {
notifyAllListenersAboutError(response);
}
}
private void notifyAllListenersAboutError(@BillingClient.BillingResponse int billingResponse) {
switch (billingResponse) {
case BillingClient.BillingResponse.BILLING_UNAVAILABLE:
case BillingClient.BillingResponse.SERVICE_UNAVAILABLE:
case BillingClient.BillingResponse.DEVELOPER_ERROR: {
forEachApply(new FuncUtils.MyConsumer<BillingProviderCallbacks>() {
@Override
public void accept(BillingProviderCallbacks billingProviderCallbacks) {
billingProviderCallbacks.onBillingServiceUnAvailable();
}
});
}
case BillingClient.BillingResponse.SERVICE_DISCONNECTED: {
forEachApply(new FuncUtils.MyConsumer<BillingProviderCallbacks>() {
@Override
public void accept(BillingProviderCallbacks billingProviderCallbacks) {
billingProviderCallbacks.onBillingServiceDisconnected();
}
});
break;
}
case BillingClient.BillingResponse.USER_CANCELED: {
forEachApply(new FuncUtils.MyConsumer<BillingProviderCallbacks>() {
@Override
public void accept(BillingProviderCallbacks billingProviderCallbacks) {
billingProviderCallbacks.onUserCanceledPurchase();
}
});
break;
}
case BillingClient.BillingResponse.ERROR: {
forEachApply(new FuncUtils.MyConsumer<BillingProviderCallbacks>() {
@Override
public void accept(BillingProviderCallbacks billingProviderCallbacks) {
billingProviderCallbacks.onError();
}
});
break;
}
case BillingClient.BillingResponse.ITEM_ALREADY_OWNED: {
forEachApply(new FuncUtils.MyConsumer<BillingProviderCallbacks>() {
@Override
public void accept(BillingProviderCallbacks billingProviderCallbacks) {
billingProviderCallbacks.onItemAlreadyOwned();
}
});
break;
}
}
}
}
package com.noctua.billing;
import android.app.Activity;
import android.support.annotation.Nullable;
import com.android.billingclient.api.Purchase;
import java.util.List;
/**
* Created by wldev on 8/28/17.
*/
public interface BillingProvider {
void connect();
void disconnect();
void addCallbackListener(BillingProviderCallbacks callbackListener);
void removeCallbackListener(BillingProviderCallbacks callbackListener);
boolean launchItemPurchase(Activity activity, String skuId);
boolean launchSubPurchase(Activity activity, String skuId, @Nullable String oldSku);
List<Purchase> getMyPurchases();
}
package com.noctua.billing;
import com.android.billingclient.api.Purchase;
import java.util.List;
/**
* Created by wldev on 8/28/17.
*/
public interface BillingProviderCallbacks {
void onBillingServiceConnected();
void onBillingServiceDisconnected();
void onBillingServiceUnAvailable();
void onError();
void onBillingFlowError();
void onUserCanceledPurchase();
void onPurchasesListUpdated(List<Purchase> purchases);
void onItemAlreadyOwned();
}
package com.noctua.billing;
import com.android.billingclient.api.Purchase;
import java.util.List;
/**
* Created by wldev on 9/13/17.
*/
public abstract class BillingProviderCallbacksAdapter implements BillingProviderCallbacks {
@Override
public void onBillingServiceConnected() {
}
@Override
public void onBillingServiceDisconnected() {
}
@Override
public void onBillingServiceUnAvailable() {
}
@Override
public void onError() {
}
@Override
public void onBillingFlowError() {
}
@Override
public void onUserCanceledPurchase() {
}
@Override
public void onPurchasesListUpdated(List<Purchase> purchases) {
}
@Override
public void onItemAlreadyOwned() {
}
}
package com.noctua.billing;
import android.app.Activity;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.text.TextUtils;
import android.util.Log;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientImpl;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.noctua.common.FuncUtils;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
/**
* Created by wldev on 8/28/17.
*/
public class BillingProviderImpl implements BillingProvider, PurchasesUpdatedListener, BillingClientStateListener {
private static final String TAG = BillingProviderImpl.class.getSimpleName();
private BillingClient mBillingClient;
private Handler uiHandler;
private final Context appContext;
private List<BillingProviderCallbacks> billingProviderCallbacks = new ArrayList<>();
public BillingProviderImpl(Context context) {
appContext = context;
mBillingClient = new BillingClient.Builder(context).setListener(this).build();
uiHandler = new Handler(Looper.getMainLooper());
}
@Override
public void connect() {
if (Looper.myLooper() == Looper.getMainLooper()) {
connectOrCreate();
} else {
connectOnUI();
}
}
@Override
public void disconnect() {
if (billingProviderCallbacks.isEmpty()) {
if (Looper.myLooper() == Looper.getMainLooper()) {
disconnectAndClear();
} else {
disconnectOnUI();
}
} else {
Log.d(TAG, "You can't disconnect service while there is at least one listener");
}
}
private void connectOrCreate() {
Log.d(TAG, "connectOrCreate: " + (mBillingClient == null));
if (mBillingClient == null) {
mBillingClient = new BillingClientImpl.Builder(appContext).setListener(this).build();
}
mBillingClient.startConnection(this);
}
private void disconnectAndClear() {
Log.d(TAG, "disconnectAndClear: " + (mBillingClient != null));
if (mBillingClient != null) {
mBillingClient.endConnection();
mBillingClient = null;
}
}
@Override
public void addCallbackListener(BillingProviderCallbacks callbackListener) {
billingProviderCallbacks.add(callbackListener);
}
@Override
public void removeCallbackListener(BillingProviderCallbacks callbackListener) {
billingProviderCallbacks.remove(callbackListener);
}
@Override
public boolean launchItemPurchase(final Activity activity, final String skuId) {
if (mBillingClient.isReady()) {
if (Looper.myLooper() == Looper.getMainLooper()) {
launchBillingFlow(activity, BillingClient.SkuType.INAPP, skuId, null);
} else {
launchItemPurchaseOnUI(activity, skuId);
}
return true;
} else {
return false;
}
}
@Override
public boolean launchSubPurchase(final Activity activity, final String skuId, @Nullable final String lastSubSku) {
if (mBillingClient.isReady()) {
if (Looper.myLooper() == Looper.getMainLooper()) {
launchBillingFlow(activity, BillingClient.SkuType.SUBS, skuId, lastSubSku);
} else {
launchSubPurchaseOnUI(activity, skuId, lastSubSku);
}
return true;
} else {
return false;
}
}
@Override
public void onBillingSetupFinished(@BillingClient.BillingResponse int resultCode) {
Log.d(TAG, "onBillingSetupFinished() called with: resultCode = [" + resultCode + "]");
if (billingProviderCallbacks != null) {
switch (resultCode) {
case BillingClient.BillingResponse.OK: {
forEachApply(new FuncUtils.MyConsumer<BillingProviderCallbacks>() {
@Override
public void accept(BillingProviderCallbacks billingProviderCallbacks) {
billingProviderCallbacks.onBillingServiceConnected();
}
});
break;
}
default: {
notifyAllListenersAboutError(resultCode);
break;
}
}
}
}
@Override
public void onBillingServiceDisconnected() {
forEachApply(new FuncUtils.MyConsumer<BillingProviderCallbacks>() {
@Override
public void accept(BillingProviderCallbacks billingProviderCallbacks) {
billingProviderCallbacks.onBillingServiceDisconnected();
}
});
}
@Override
public void onPurchasesUpdated(@BillingClient.BillingResponse int responseCode, List<Purchase> purchases) {
Log.d(TAG, "onPurchasesUpdated() called with: responseCode = [" + responseCode + "], purchases = [" + purchases + "]");
if (billingProviderCallbacks != null) {
switch (responseCode) {
case BillingClient.BillingResponse.OK: {
handlePurchases(purchases);
break;
}
default: {
notifyAllListenersAboutError(responseCode);
break;
}
}
}
}
private void handlePurchases(@Nullable final List<Purchase> purchases) {
forEachApply(new FuncUtils.MyConsumer<BillingProviderCallbacks>() {
@Override
public void accept(BillingProviderCallbacks billingProviderCallbacks) {
billingProviderCallbacks.onPurchasesListUpdated(purchases);
}
});
}
private void launchItemPurchaseOnUI(final Activity activity, final String skuId) {
final WeakReference<Activity> activityWeakReference = new WeakReference<>(activity);
final WeakReference<String> skuIDWeakReference = new WeakReference<>(skuId);
uiHandler.post(new Runnable() {
@Override
public void run() {
if (activityWeakReference.get() != null && skuIDWeakReference.get() != null) {
launchBillingFlow(activityWeakReference.get(), BillingClient.SkuType.INAPP, skuId, null);
}
}
});
}
private void launchSubPurchaseOnUI(Activity activity, final String skuId, @Nullable final String oldSku) {
final WeakReference<Activity> activityWeakReference = new WeakReference<>(activity);
final WeakReference<String> skuIDWeakReference = new WeakReference<>(skuId);
uiHandler.post(new Runnable() {
@Override
public void run() {
if (activityWeakReference.get() != null && skuIDWeakReference.get() != null) {
launchBillingFlow(activityWeakReference.get(), BillingClient.SkuType.SUBS, skuId, oldSku);
}
}
});
}
@UiThread
private void connectOnUI() {
if (!mBillingClient.isReady()) {
uiHandler.post(new Runnable() {
@Override
public void run() {
connectOrCreate();
}
});
}
}
@UiThread
private void disconnectOnUI() {
if (mBillingClient.isReady()) {
uiHandler.post(new Runnable() {
@Override
public void run() {
disconnectAndClear();
}
});
}
}
private void forEachApply(@NonNull FuncUtils.MyConsumer<? super BillingProviderCallbacks> consumer) {
for (int i = 0; i < billingProviderCallbacks.size(); i++) {
consumer.accept(billingProviderCallbacks.get(i));
}
}
@Override
public List<Purchase> getMyPurchases() {
List<Purchase> purchases = new ArrayList<>();
if (mBillingClient != null && mBillingClient.isReady()) {
Purchase.PurchasesResult inApps = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
if (inApps != null && inApps.getPurchasesList() != null) {
purchases.addAll(inApps.getPurchasesList());
}
Purchase.PurchasesResult subs = mBillingClient.queryPurchases(BillingClient.SkuType.SUBS);
if (subs != null && subs.getPurchasesList() != null) {
purchases.addAll(subs.getPurchasesList());
}
}
Log.d(TAG, "getMyPurchases() returned: " + purchases.size());
return purchases;
}
private void launchBillingFlow(Activity activity, @BillingClient.SkuType String skuType, String sku, @Nullable String oldSku) {
BillingFlowParams.Builder paramsBuilder = new BillingFlowParams.Builder().setSku(sku).setType(skuType);
if (!TextUtils.isEmpty(oldSku)) {
paramsBuilder.addOldSku(oldSku);
}
int response = mBillingClient.launchBillingFlow(activity, paramsBuilder.build());
if (response != BillingClient.BillingResponse.OK) {
notifyAllListenersAboutError(response);
}
}
private void notifyAllListenersAboutError(@BillingClient.BillingResponse int billingResponse) {
switch (billingResponse) {
case BillingClient.BillingResponse.BILLING_UNAVAILABLE:
case BillingClient.BillingResponse.SERVICE_UNAVAILABLE:
case BillingClient.BillingResponse.DEVELOPER_ERROR: {
forEachApply(new FuncUtils.MyConsumer<BillingProviderCallbacks>() {
@Override
public void accept(BillingProviderCallbacks billingProviderCallbacks) {
billingProviderCallbacks.onBillingServiceUnAvailable();
}
});
}
case BillingClient.BillingResponse.SERVICE_DISCONNECTED: {
forEachApply(new FuncUtils.MyConsumer<BillingProviderCallbacks>() {
@Override
public void accept(BillingProviderCallbacks billingProviderCallbacks) {
billingProviderCallbacks.onBillingServiceDisconnected();
}
});
break;
}
case BillingClient.BillingResponse.USER_CANCELED: {
forEachApply(new FuncUtils.MyConsumer<BillingProviderCallbacks>() {
@Override
public void accept(BillingProviderCallbacks billingProviderCallbacks) {
billingProviderCallbacks.onUserCanceledPurchase();
}
});
break;
}
case BillingClient.BillingResponse.ERROR: {
forEachApply(new FuncUtils.MyConsumer<BillingProviderCallbacks>() {
@Override
public void accept(BillingProviderCallbacks billingProviderCallbacks) {
billingProviderCallbacks.onError();
}
});
break;
}
case BillingClient.BillingResponse.ITEM_ALREADY_OWNED: {
forEachApply(new FuncUtils.MyConsumer<BillingProviderCallbacks>() {
@Override
public void accept(BillingProviderCallbacks billingProviderCallbacks) {
billingProviderCallbacks.onItemAlreadyOwned();
}
});
break;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment