Skip to content

[Plans] Integrate Google Store calls, and synch IAP with backend - step one#4078

Merged
nbradbury merged 30 commits intodevelopfrom
feature/plans-integrate-store-step-one
May 12, 2016
Merged

[Plans] Integrate Google Store calls, and synch IAP with backend - step one#4078
nbradbury merged 30 commits intodevelopfrom
feature/plans-integrate-store-step-one

Conversation

@daniloercoli
Copy link
Copy Markdown
Contributor

This PR integrates Google Store calls into the app, and syncs in-app purchases with the backend.

In details:

  • When the user taps on the Buy button the app starts the Google Store purchase Intent.
  • When the purchase is finished on the Store, the app gets back the result, and takes the user to the post IAP flow. If there is an error the Google Store already shows an error, no need to show anything here in the app, and the app keeps the user on the Plans UI.
  • Immediately after the purchase is made, the app syncs with the wpcom servers by running a refresh task.

The refresh task, is something that syncs all the IAP info with the backend immediately after a purchase is made, at app startup, and every 5 minutes. This is just a security check, so we don't need to store in the app if the latest upgrades were synched with the server with success or not. Also, the user can cancel the subscription in the Google Store App, and we don't know this fact until the app is launched again (or the backend runs the nightly checks).

This PR also disable Buy button if a paid plan is already available on the blog. Upgrades aren't handled in-app in this iteration.

Note: Backend is not yet ready. You will see 404 errors on those network calls ;)

…e user could have cancelled them on the PlayStore).

Re-synch purchases when the user buys a subscription from the store.
Add comments.
…an subscription, and redirect the user to support.

We're not going to enable upgrades whitin the apps.
…ss-Android into feature/plans-integrate-store-step-one

# Conflicts:
#	WordPress/src/main/res/values/strings.xml
@daniloercoli daniloercoli changed the title [Plans] Integrate google store and synch IAP with backend - step one [Plans] Integrate Google Store calls, and synch IAP with backend - step one May 9, 2016
@nbradbury
Copy link
Copy Markdown
Contributor

I just tried this PR on a Nexus 6 emulator with the Google APIs, and at startup I'm seeing this:

Caused by: java.lang.IllegalStateException: IAB helper is not set up. 
Can't perform operation: queryInventory
                                                                         at org.wordpress.android.ui.plans.util.IabHelper.checkSetupDone(IabHelper.java:808)
                                                                         at org.wordpress.android.ui.plans.util.IabHelper.queryInventory(IabHelper.java:568)
                                                                         at org.wordpress.android.ui.plans.UpdateIAPTask.listIAPs(UpdateIAPTask.java:88)
                                                                         at org.wordpress.android.ui.plans.UpdateIAPTask.doInBackground(UpdateIAPTask.java:78)
                                                                         at org.wordpress.android.ui.plans.UpdateIAPTask.doInBackground(UpdateIAPTask.java:31)
                                                                         at android.os.AsyncTask$2.call(AsyncTask.java:295)
                                                                         at java.util.concurrent.FutureTask.run(FutureTask.java:237)
                                                                         at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113) 
                                                                         at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588) 
                                                                         at java.lang.Thread.run(Thread.java:818) 

@daniloercoli
Copy link
Copy Markdown
Contributor Author

@nbradbury You should see a IAB failed with XXX error before that one you mentioned above.
Anyway, I probably missed to check if IAB was setup with success or not before starting the network calls.
Also thinking to disable the Buy button if for some reason IABHelper cannot be successfully setup on the device.

final Plan plan = getPageAdapter().getPlan(position);

// No downgrade!
if (PlansUtils.isFreePlan(plan) || currentPlanProductId > plan.getProductID()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than hard-code currentPlanProductId > plan.getProductID(), I think we should have a helper routine that checks if one plan is "greater" than another. We had this earlier on, and it was added to avoid sprinkling our code with assumptions that a greater plan ID meant a more expensive plan.

Introduce a utlity method that check if a plan is "greater" than another.
Fix a typo in the method that returns if a passed plan object is a free plan.
@nbradbury
Copy link
Copy Markdown
Contributor

I'm wondering if we can make this more efficient.

Every five minutes it makes a call to the IabHelper to get purchases (which by itself is fine since purchases are cached on the device), and then makes a separate POST to our backend for each purchase.

This is fine for users who never buy anything since no network calls will be made, but otherwise it seems wasteful.

I think we're okay decreasing the frequency of UpdateIAPTask. As long as it's called at startup, changing the interval to 30min or longer seems more reasonable. And it would be nice if the backend could accept multiple purchases in a single call.

Even better would be some way to determine whether we even need to call our backend to update purchases, since the vast majority of the time our backend will already have the latest purchase info.

This may seem like nitpicking since we're not talking about a huge waste of bandwidth, but over time I've seen us increase the number of network calls done at startup and at regular intervals, and I think we need to be more careful about unnecessary network usage.

return plan.getProductSlug().equals("free_plan") ||
plan.getProductSlug().equals("jetpack_free");
return plan.getProductSlug().equals(PlansConstants.FREE_PLAN_SLUG) ||
plan.getProductSlug().equals(PlansConstants.JETPACK_FREE_PLAN_SLUG);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@daniloercoli
Copy link
Copy Markdown
Contributor Author

I think we're okay decreasing the frequency of UpdateIAPTask. As long as it's called at startup, changing the interval to 30min or longer seems more reasonable.

Ok, will update the interval to 60 minutes, since it's already called at startup.

Even better would be some way to determine whether we even need to call our backend to update purchases, since the vast majority of the time our backend will already have the latest purchase info.

Yeah, I had thought to implement this kind of logic, but ended up with a code that was a bit clunky. We need to keep track if the purchase was already synched with the server, and keep track of server responses. Anyway, will try to rethink it.
It would not be a problem if our code that checks purchase isn't fail-proof, since the backend runs a nightly task that checks all the IAPs by contacting Google Store directly.

…not use scheduled updates, since the backend already synchs the IAPs nightly.
@daniloercoli
Copy link
Copy Markdown
Contributor Author

@nbradbury - This is ready for another round of review.
IAPs are only synched when a purchase is made. We don't need to check them client side periodically, since the backend already synchs them on a nightly job.

@nbradbury
Copy link
Copy Markdown
Contributor

nbradbury commented May 12, 2016

IAPs are only synched when a purchase is made. We don't need to check them client side periodically, since the backend already synchs them on a nightly job.

Perhaps I'm misunderstanding, but if this is the case, how does the app know about purchases made elsewhere (on the web, or on another device, for example)?

Nevermind, I answered my own question by looking at your last commit.

public class UpdateIAPTask extends AsyncTask<Void, Void, Void> {
private static final int GET_IAP_BINDER_TIMEOUT = 30000;
private static final String IAP_ENDPOINT = "/iap/validate";
protected Context mContext;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit, this can be private final rather than protected.

@daniloercoli
Copy link
Copy Markdown
Contributor Author

@nbradbury - I think all the issues brought up during the 2nd review are now resolved.


void logError(String msg) {
AppLog.d(AppLog.T.PLANS, "In-app billing error: " + msg);
AppLog.e(AppLog.T.PLANS, "In-app billing error: " + msg);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both logError and logWarn are missing the mDebugLog check.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original version doesn't have the mDebugLog check for w and e logging. I think it's ok not having it there, since w and e are important.

@nbradbury
Copy link
Copy Markdown
Contributor

:shipit:

@nbradbury nbradbury merged commit f963389 into develop May 12, 2016
@nbradbury nbradbury deleted the feature/plans-integrate-store-step-one branch May 12, 2016 19:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants