Skip to content

Commit

Permalink
Merge pull request #73 from vedidev/impl-subs
Browse files Browse the repository at this point in the history
Feature #99 Implement Google Play Subscriptions
  • Loading branch information
EugeneButusov committed May 23, 2016
2 parents 3166783 + f0f823e commit 6924743
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 11 deletions.
71 changes: 68 additions & 3 deletions SoomlaAndroidStore/src/com/soomla/store/SoomlaStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.soomla.SoomlaConfig;
import com.soomla.SoomlaUtils;
import com.soomla.store.billing.IIabService;
import com.soomla.store.billing.IabHelper;
import com.soomla.store.billing.IabCallbacks;
import com.soomla.store.billing.IabException;
import com.soomla.store.billing.IabPurchase;
Expand All @@ -32,6 +33,7 @@
import com.soomla.store.domain.MarketItem;
import com.soomla.store.domain.PurchasableVirtualItem;
import com.soomla.store.domain.VirtualItem;
import com.soomla.store.domain.virtualGoods.VirtualGood;
import com.soomla.store.events.BillingNotSupportedEvent;
import com.soomla.store.events.BillingSupportedEvent;
import com.soomla.store.events.IabServiceStartedEvent;
Expand Down Expand Up @@ -214,7 +216,54 @@ public void verificationStarted(List<IabPurchase> purchases) {
}
};

BusProvider.getInstance().post(new RestoreTransactionsStartedEvent());
IabCallbacks.OnRestorePurchasesListener restoreSubscriptionsListener = new IabCallbacks.OnRestorePurchasesListener() {
@Override
public void success(List<IabPurchase> purchases) {

// collect subscription ids list
List<String> subscriptionIds = new ArrayList<String>();
for (IabPurchase purchase : purchases) {
subscriptionIds.add(purchase.getSku());
}

// collect subscriptionVG list
List<VirtualGood> subscriptions = new ArrayList<VirtualGood>();
for (VirtualGood good : StoreInfo.getGoods()) {
if ((good.getPurchaseType() instanceof PurchaseWithMarket) && ((PurchaseWithMarket)good.getPurchaseType()).isSubscription()) {
subscriptions.add(good);
}
}

// give unset subscriptions and take expired
for (VirtualGood subscription : subscriptions) {
String productId = ((PurchaseWithMarket)subscription.getPurchaseType()).getMarketItem().getProductId();
if (subscriptionIds.contains(productId)) {
// TODO: is here should be 1 to give? Maybe current item has not only just 0/1 state
subscription.give(1, false);
} else {
try {
subscription.take(StoreInventory.getVirtualItemBalance(subscription.getItemId()), false);
}
catch (VirtualItemNotFoundException ex) {
// possibly unreachable block
}
}
}
// TODO: Should we notify user about repaired or expired subscriptions?
}

@Override
public void fail(String message) {
SoomlaUtils.LogDebug(TAG, "Subscriptions restoring failed: " + message);
}

@Override
public void verificationStarted(List<IabPurchase> purchases) {
// should we do it in subscription restoring? possibly it should be empty
}
};

// no events like in restore purchases - keep subscription restoring silent for end-user

try {
mInAppBillingService.restorePurchasesAsync(restorePurchasesListener);
Expand Down Expand Up @@ -369,6 +418,19 @@ public void refreshInventory() {
* @throws IllegalStateException
*/
public void buyWithMarket(final MarketItem marketItem, final String payload) throws IllegalStateException {
buyWithMarket(marketItem, false, payload);
}

/**
* Starts a purchase process in the market.
*
* @param marketItem The item to purchase - this item has to be defined EXACTLY the same in
* the market
* @param isSubscription determines if subscription is purchasing
* @param payload A payload to get back when this purchase is finished.
* @throws IllegalStateException
*/
public void buyWithMarket(final MarketItem marketItem, final boolean isSubscription, final String payload) throws IllegalStateException {
if (mInAppBillingService == null) {
SoomlaUtils.LogError(TAG, "Billing service is not loaded. Can't invoke buyWithMarket.");
return;
Expand Down Expand Up @@ -447,8 +509,11 @@ public void verificationStarted(List<IabPurchase> purchases) {
BusProvider.getInstance().post(new MarketPurchaseStartedEvent(pvi, getInAppBillingService().shouldVerifyPurchases()));

try {
mInAppBillingService.launchPurchaseFlow(marketItem.getProductId(),
purchaseListener, payload);
if (isSubscription) {
mInAppBillingService.launchPurchaseFlow(IabHelper.ITEM_TYPE_SUBS, marketItem.getProductId(), purchaseListener, payload);
} else {
mInAppBillingService.launchPurchaseFlow(IabHelper.ITEM_TYPE_INAPP, marketItem.getProductId(), purchaseListener, payload);
}
} catch (IllegalStateException ex) {
SoomlaUtils.LogError(TAG, "Can't proceed with launchPurchaseFlow. error: " + ex.getMessage());
purchaseListener.fail("Can't proceed with launchPurchaseFlow. error: " + ex.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,13 @@ void consumeAsync(IabPurchase purchase,
* @param extraData extra data (developer payload), which will be returned with the purchase
* data when the purchase completes.
*/
void launchPurchaseFlow(String sku,
void launchPurchaseFlow(String itemType,
String sku,
final IabCallbacks.OnPurchaseListener purchaseListener,
String extraData);

/**
* Restores transactions asynchronously. This operation will get all previously purchased
* Restores purchases asynchronously. This operation will get all previously purchased
* non-consumables and invoke the given callback.
*
* @param restorePurchasesListener the listener to notify when the query operation completes.
Expand Down
36 changes: 33 additions & 3 deletions SoomlaAndroidStore/src/com/soomla/store/billing/IabHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,32 @@ public synchronized void startSetup(final OnIabSetupFinishedListener listener) {
}
}

/**
* 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 #} method to continue the purchase flow. This method
* MUST be called from the UI thread of the Activity.
*
* @param act The calling activity.
* @param itemType ITEM_TYPE_INAPP or ITEM_TYPE_SUBS
* @param sku The sku of the item to purchase.
* @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 itemType, String sku,
OnIabPurchaseFinishedListener listener, String extraData) {
checkSetupDoneAndThrow("launchPurchaseFlow");
flagStartAsync("launchPurchaseFlow");

mPurchaseListener = listener;
mLastOperationSKU = sku;
launchPurchaseFlowInner(act, itemType, sku, 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
Expand All @@ -97,7 +123,7 @@ public void launchPurchaseFlow(Activity act, String sku,

mPurchaseListener = listener;
mLastOperationSKU = sku;
launchPurchaseFlowInner(act, sku, extraData);
launchPurchaseFlowInner(act, ITEM_TYPE_INAPP, sku, extraData);
}

/**
Expand Down Expand Up @@ -202,7 +228,7 @@ public interface FetchSkusDetailsFinishedListener {

// Item types
public static final String ITEM_TYPE_INAPP = "inapp";
// public static final String ITEM_TYPE_SUBS = "subs"; // Subscriptions are not supported
public static final String ITEM_TYPE_SUBS = "subs";



Expand All @@ -216,7 +242,7 @@ public interface FetchSkusDetailsFinishedListener {
/**
* see launchPurchaseFlow
*/
protected abstract void launchPurchaseFlowInner(Activity act, String sku, String extraData);
protected abstract void launchPurchaseFlowInner(Activity act, String itemType, String sku, String extraData);

/**
* see restorePurchasesAsync
Expand Down Expand Up @@ -512,6 +538,10 @@ protected void setRvsProductionMode(boolean rvsProductionMode) {
// The listener registered on restore purchases, which we have to call back when
// the restore process finishes.
private RestorePurchasessFinishedListener mRestorePurchasessFinishedListener;
// The listener registered on restore subscriptions, which we have to call back when
// the restore process finishes.
private RestorePurchasessFinishedListener mRestoreSubscriptionsFinishedListener;

// The listener registered on restore purchases, which we have to call back when
// the restore process finishes.
private FetchSkusDetailsFinishedListener mFetchSkusDetailsFinishedListener;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,43 @@ public class PurchaseWithMarket extends PurchaseType {
*
* @param productId the productId as it appears in the Market.
* @param price the price in the Market.
* @param isSubscription let PurchaseWithMarket know is current purchase is subscription.
*/
public PurchaseWithMarket(String productId, double price) {
public PurchaseWithMarket(String productId, double price, boolean isSubscription) {
mMarketItem = new MarketItem(productId, price);
mIsSubscription = isSubscription;
}

/**
* Constructor.
* Constructs a PurchaseWithMarket object by constructing a new <code>MarketItem</code> object
* with the given <code>productId</code> and price, and declaring it as UNMANAGED.
*
* @param productId the productId as it appears in the Market.
* @param price the price in the Market.
*/
public PurchaseWithMarket(String productId, double price) {
this(productId, price, false);
}

/**
* Constructor.
*
* @param marketItem the representation of the item in the market
* @param isSubscription let PurchaseWithMarket know is current purchase is subscription.
*/
public PurchaseWithMarket(MarketItem marketItem) {
public PurchaseWithMarket(MarketItem marketItem, boolean isSubscription) {
mMarketItem = marketItem;
mIsSubscription = isSubscription;
}

/**
* Constructor.
*
* @param marketItem the representation of the item in the market
*/
public PurchaseWithMarket(MarketItem marketItem) {
this(marketItem, false);
}

/**
Expand All @@ -64,7 +89,7 @@ public void buy(String payload) throws InsufficientFundsException {

BusProvider.getInstance().post(new ItemPurchaseStartedEvent(getAssociatedItem().getItemId()));
try {
SoomlaStore.getInstance().buyWithMarket(mMarketItem, payload);
SoomlaStore.getInstance().buyWithMarket(mMarketItem, mIsSubscription, payload);
} catch (IllegalStateException e) {
SoomlaUtils.LogError(TAG, "Error when purchasing item");
}
Expand All @@ -77,10 +102,15 @@ public MarketItem getMarketItem() {
return mMarketItem;
}

public boolean isSubscription() {
return mIsSubscription;
}


/** Private Members */

private static final String TAG = "SOOMLA PurchaseWithMarket"; //used for Log messages

private MarketItem mMarketItem; //the representation of the item in the market
private boolean mIsSubscription; //allows SoomlaStore know if this purchase is subscription
}

0 comments on commit 6924743

Please sign in to comment.